+/* Version numbers of the debug format we understand */
+#define VER_MAJOR 1U
+#define VER_MINOR 0U
+
/* Dynamic strings */
typedef struct StrBuf StrBuf;
struct StrBuf {
TOK_MINOR, /* MINOR keyword */
TOK_MTIME, /* MTIME keyword */
TOK_NAME, /* NAME keyword */
+ TOK_OUTPUTNAME, /* OUTPUTNAME keyword */
+ TOK_OUTPUTOFFS, /* OUTPUTOFFS keyword */
TOK_RANGE, /* RANGE keyword */
TOK_RO, /* RO keyword */
TOK_RW, /* RW keyword */
unsigned Id; /* Id of segment */
cc65_addr Start; /* Start address of segment */
cc65_addr Size; /* Size of segment */
+ char* OutputName; /* Name of output file */
+ unsigned long OutputOffs; /* Offset in output file */
char SegName[1]; /* Name of segment */
};
+static char* SB_StrDup (const StrBuf* B)
+/* Return the contents of B as a dynamically allocated string. The string
+ * will always be NUL terminated.
+ */
+{
+ /* Allocate memory */
+ char* S = xmalloc (B->Len + 1);
+
+ /* Copy the string */
+ memcpy (S, B->Buf, B->Len);
+
+ /* Terminate it */
+ S[B->Len] = '\0';
+
+ /* And return the result */
+ return S;
+}
+
+
+
/*****************************************************************************/
/* Collections */
/*****************************************************************************/
static SegInfo* NewSegInfo (const StrBuf* SegName, unsigned Id,
- cc65_addr Start, cc65_addr Size)
+ cc65_addr Start, cc65_addr Size,
+ const StrBuf* OutputName, unsigned long OutputOffs)
/* Create a new SegInfo struct and return it */
{
/* Allocate memory */
SegInfo* S = xmalloc (sizeof (SegInfo) + SB_GetLen (SegName));
/* Initialize it */
- S->Id = Id;
- S->Start = Start;
- S->Size = Size;
+ S->Id = Id;
+ S->Start = Start;
+ S->Size = Size;
+ if (SB_GetLen (OutputName) > 0) {
+ /* Output file given */
+ S->OutputName = SB_StrDup (OutputName);
+ S->OutputOffs = OutputOffs;
+ } else {
+ /* No output file given */
+ S->OutputName = 0;
+ S->OutputOffs = 0;
+ }
memcpy (S->SegName, SB_GetConstBuf (SegName), SB_GetLen (SegName) + 1);
/* Return it */
+static void CopyLineInfo (cc65_linedata* D, const LineInfo* L)
+/* Copy data from a LineInfo struct to the cc65_linedata struct returned to
+ * the caller.
+ */
+{
+ D->source_name = L->File.Info->FileName;
+ D->source_size = L->File.Info->Size;
+ D->source_mtime = L->File.Info->MTime;
+ D->source_line = L->Line;
+ D->line_start = L->Start;
+ D->line_end = L->End;
+ if (L->Seg.Info->OutputName) {
+ D->output_name = L->Seg.Info->OutputName;
+ D->output_offs = L->Seg.Info->OutputOffs + L->Start - L->Seg.Info->Start;
+ } else {
+ D->output_name = 0;
+ D->output_offs = 0;
+ }
+}
+
+
+
static void ParseError (InputData* D, cc65_error_severity Type, const char* Msg, ...)
/* Call the user supplied parse error function */
{
-static void MissingAttribute (InputData* D, const char* AttrName)
-/* Print an error about a missing attribute */
-{
- ParseError (D, CC65_ERROR, "Attribute \"%s\" is mising", AttrName);
-}
-
-
-
static void UnknownKeyword (InputData* D)
/* Print a warning about an unknown keyword in the file. Try to do smart
* recovery, so if later versions of the debug information add additional
/* Read the next token from the input stream */
{
static const struct KeywordEntry {
- const char Keyword[10];
+ const char Keyword[16];
Token Tok;
} KeywordTable[] = {
{ "absolute", TOK_ABSOLUTE },
{ "minor", TOK_MINOR },
{ "mtime", TOK_MTIME },
{ "name", TOK_NAME },
+ { "outputname", TOK_OUTPUTNAME },
+ { "outputoffs", TOK_OUTPUTOFFS },
{ "range", TOK_RANGE },
{ "ro", TOK_RO },
{ "rw", TOK_RW },
+static void ConsumeEOL (InputData* D)
+/* Consume an end-of-line token, if we aren't at end-of-file */
+{
+ if (D->Tok != TOK_EOF) {
+ if (D->Tok != TOK_EOL) {
+ ParseError (D, CC65_ERROR, "Extra tokens in line");
+ SkipLine (D);
+ }
+ NextToken (D);
+ }
+}
+
+
+
static void ParseFile (InputData* D)
/* Parse a FILE line */
{
}
/* Check for required information */
- if (InfoBits != ibRequired) {
+ if ((InfoBits & ibRequired) != ibRequired) {
ParseError (D, CC65_ERROR, "Required attributes missing");
goto ErrorExit;
}
}
/* Check for required information */
- if (InfoBits != ibRequired) {
+ if ((InfoBits & ibRequired) != ibRequired) {
ParseError (D, CC65_ERROR, "Required attributes missing");
goto ErrorExit;
}
static void ParseSegment (InputData* D)
/* Parse a SEGMENT line */
{
- unsigned Id;
- cc65_addr Start;
- cc65_addr Size;
- StrBuf SegName = STRBUF_INITIALIZER;
- SegInfo* S;
+ unsigned Id;
+ cc65_addr Start;
+ cc65_addr Size;
+ StrBuf SegName = STRBUF_INITIALIZER;
+ StrBuf OutputName = STRBUF_INITIALIZER;
+ unsigned long OutputOffs;
+ SegInfo* S;
enum {
ibNone = 0x00,
ibId = 0x01,
ibSize = 0x08,
ibAddrSize = 0x10,
ibType = 0x20,
+ ibOutputName= 0x40,
+ ibOutputOffs= 0x80,
ibRequired = ibId | ibSegName | ibStart | ibSize | ibAddrSize | ibType,
} InfoBits = ibNone;
}
/* Something we know? */
- if (D->Tok != TOK_ID && D->Tok != TOK_NAME &&
- D->Tok != TOK_START && D->Tok != TOK_SIZE &&
- D->Tok != TOK_ADDRSIZE && D->Tok != TOK_TYPE) {
+ if (D->Tok != TOK_ADDRSIZE && D->Tok != TOK_ID &&
+ D->Tok != TOK_NAME && D->Tok != TOK_OUTPUTNAME &&
+ D->Tok != TOK_OUTPUTOFFS && D->Tok != TOK_SIZE &&
+ D->Tok != TOK_START && D->Tok != TOK_TYPE) {
/* Done */
break;
}
NextToken (D);
break;
+ case TOK_OUTPUTNAME:
+ if (!StrConstFollows (D)) {
+ goto ErrorExit;
+ }
+ SB_Copy (&OutputName, &D->SVal);
+ SB_Terminate (&OutputName);
+ InfoBits |= ibOutputName;
+ NextToken (D);
+ break;
+
+ case TOK_OUTPUTOFFS:
+ if (!IntConstFollows (D)) {
+ goto ErrorExit;
+ }
+ OutputOffs = D->IVal;
+ NextToken (D);
+ InfoBits |= ibOutputOffs;
+ break;
+
case TOK_START:
if (!IntConstFollows (D)) {
goto ErrorExit;
}
/* Check for required information */
- if (InfoBits != ibRequired) {
+ if ((InfoBits & ibRequired) != ibRequired) {
ParseError (D, CC65_ERROR, "Required attributes missing");
goto ErrorExit;
}
/* Create the segment info and remember it */
- S = NewSegInfo (&SegName, Id, Start, Size);
+ S = NewSegInfo (&SegName, Id, Start, Size, &OutputName, OutputOffs);
CollAppend (&D->Info->SegInfoByName, S);
ErrorExit:
/* Entry point in case of errors */
SB_Done (&SegName);
+ SB_Done (&OutputName);
return;
}
static void ParseVersion (InputData* D)
/* Parse a VERSION line */
{
- enum { None = 0x00, Major = 0x01, Minor = 0x02 } InfoBits = None;
+ enum {
+ ibNone = 0x00,
+ ibMajor = 0x01,
+ ibMinor = 0x02,
+ ibRequired = ibMajor | ibMinor,
+ } InfoBits = ibNone;
/* Skip the VERSION token */
NextToken (D);
}
D->MajorVersion = D->IVal;
NextToken (D);
- InfoBits |= Major;
+ InfoBits |= ibMajor;
break;
case TOK_MINOR:
}
D->MinorVersion = D->IVal;
NextToken (D);
- InfoBits |= Minor;
+ InfoBits |= ibMinor;
break;
case TOK_IDENT:
}
/* Check for required information */
- if ((InfoBits & Major) == None) {
- MissingAttribute (D, "major");
- goto ErrorExit;
- }
- if ((InfoBits & Minor) == None) {
- MissingAttribute (D, "minor");
+ if ((InfoBits & ibRequired) != ibRequired) {
+ ParseError (D, CC65_ERROR, "Required attributes missing");
goto ErrorExit;
}
/* Prime the pump */
NextToken (&D);
- /* Parse lines */
- while (D.Tok != TOK_EOF) {
+ /* The first line in the file must specify version information */
+ if (D.Tok != TOK_VERSION) {
+ ParseError (&D, CC65_ERROR,
+ "\"version\" keyword missing in first line - this is not "
+ "a valid debug info file");
+ } else {
- switch (D.Tok) {
+ /* Parse the version directive and check the version */
+ ParseVersion (&D);
+ if (D.MajorVersion > VER_MAJOR) {
+ ParseError (&D, CC65_WARNING,
+ "The format of this debug info file is newer than what we "
+ "know. Will proceed but probably fail. Version found = %u, "
+ "version supported = %u",
+ D.MajorVersion, VER_MAJOR);
+ }
+ ConsumeEOL (&D);
- case TOK_FILE:
- ParseFile (&D);
- break;
+ /* Parse lines */
+ while (D.Tok != TOK_EOF) {
- case TOK_LINE:
- ParseLine (&D);
- break;
+ switch (D.Tok) {
- case TOK_SEGMENT:
- ParseSegment (&D);
- break;
+ case TOK_FILE:
+ ParseFile (&D);
+ break;
- case TOK_SYM:
- ParseSym (&D);
- break;
+ case TOK_LINE:
+ ParseLine (&D);
+ break;
- case TOK_VERSION:
- ParseVersion (&D);
- break;
+ case TOK_SEGMENT:
+ ParseSegment (&D);
+ break;
- case TOK_IDENT:
- /* Output a warning, then skip the line with the unknown
- * keyword that may have been added by a later version.
- */
- ParseError (&D, CC65_WARNING,
- "Unknown keyword \"%s\" - skipping",
- SB_GetConstBuf (&D.SVal));
+ case TOK_SYM:
+ ParseSym (&D);
+ break;
- SkipLine (&D);
- break;
+ case TOK_IDENT:
+ /* Output a warning, then skip the line with the unknown
+ * keyword that may have been added by a later version.
+ */
+ ParseError (&D, CC65_WARNING,
+ "Unknown keyword \"%s\" - skipping",
+ SB_GetConstBuf (&D.SVal));
- default:
- UnexpectedToken (&D);
+ SkipLine (&D);
+ break;
- }
+ default:
+ UnexpectedToken (&D);
- /* EOL or EOF must follow */
- if (D.Tok != TOK_EOF) {
- if (D.Tok != TOK_EOL) {
- ParseError (&D, 1, "Extra tokens in line");
- SkipLine (&D);
}
- NextToken (&D);
+
+ /* EOL or EOF must follow */
+ ConsumeEOL (&D);
}
}
(CollCount (&LineInfos) - 1) * sizeof (D->data[0]));
D->count = CollCount (&LineInfos);
for (I = 0; I < D->count; ++I) {
-
- /* Pointer to this info */
- LineInfo* L = CollAt (&LineInfos, I);
-
/* Copy data */
- D->data[I].name = L->File.Info->FileName;
- D->data[I].size = L->File.Info->Size;
- D->data[I].mtime = L->File.Info->MTime;
- D->data[I].line = L->Line;
- D->data[I].start = L->Start;
- D->data[I].end = L->End;
+ CopyLineInfo (D->data + I, CollAt (&LineInfos, I));
}
}
D->count = 1;
/* Copy data */
- D->data[0].name = L->File.Info->FileName;
- D->data[0].size = L->File.Info->Size;
- D->data[0].mtime = L->File.Info->MTime;
- D->data[0].line = L->Line;
- D->data[0].start = L->Start;
- D->data[0].end = L->End;
+ CopyLineInfo (D->data, L);
/* Return the allocated struct */
return D;
-cc65_filelist* cc65_get_filelist (cc65_dbginfo Handle)
-/* Return a list of all files referenced in the debug information */
+cc65_sourceinfo* cc65_get_sourcelist (cc65_dbginfo Handle)
+/* Return a list of all source files */
{
- DbgInfo* Info;
- Collection* FileInfoByName;
- cc65_filelist* D;
- unsigned I;
+ DbgInfo* Info;
+ Collection* FileInfoByName;
+ cc65_sourceinfo* D;
+ unsigned I;
/* Check the parameter */
assert (Handle != 0);
FileInfo* F = CollAt (FileInfoByName, I);
/* Copy the data */
- D->data[I].name = F->FileName;
- D->data[I].size = F->Size;
- D->data[I].mtime = F->MTime;
+ D->data[I].source_name = F->FileName;
+ D->data[I].source_size = F->Size;
+ D->data[I].source_mtime = F->MTime;
}
/* Return the result */
-void cc65_free_filelist (cc65_dbginfo Handle, cc65_filelist* List)
-/* Free a file list returned by cc65_get_filelist() */
+void cc65_free_sourceinfo (cc65_dbginfo Handle, cc65_sourceinfo* Info)
+/* Free a source info record */
{
/* Just for completeness, check the handle */
assert (Handle != 0);
- /* Just free the memory */
- xfree (List);
+ /* Free the memory */
+ xfree (Info);
}
-cc65_segmentlist* cc65_get_segmentlist (cc65_dbginfo Handle)
+cc65_segmentinfo* cc65_get_segmentlist (cc65_dbginfo Handle)
/* Return a list of all segments referenced in the debug information */
{
DbgInfo* Info;
Collection* SegInfoByName;
- cc65_segmentlist* D;
+ cc65_segmentinfo* D;
unsigned I;
/* Check the parameter */
SegInfo* S = CollAt (SegInfoByName, I);
/* Copy the data */
- D->data[I].name = S->SegName;
- D->data[I].start = S->Start;
- D->data[I].end = S->Start + S->Size - 1;
+ D->data[I].segment_name = S->SegName;
+ D->data[I].segment_start = S->Start;
+ D->data[I].segment_size = S->Size;
+ D->data[I].output_name = S->OutputName;
+ D->data[I].output_offs = S->OutputOffs;
}
/* Return the result */
-void cc65_free_segmentlist (cc65_dbginfo Handle, cc65_segmentlist* List)
-/* Free a file list returned by cc65_get_filelist() */
+void cc65_free_segmentinfo (cc65_dbginfo Handle, cc65_segmentinfo* Info)
+/* Free a segment info record */
{
/* Just for completeness, check the handle */
assert (Handle != 0);
- /* Just free the memory */
- xfree (List);
+ /* Free the memory */
+ xfree (Info);
}
/* Function that is called in case of parse errors */
typedef void (*cc65_errorfunc) (const struct cc65_parseerror*);
-/* Line information */
+/* Line information.
+ * Notes:
+ * - line_end is inclusive
+ * - output_name may be NULL if the data wasn't written to the output file
+ * (example: bss segment)
+ * - output_offs is invalid if there is no output_name, and may not be of
+ * much use in case of a relocatable output file
+ */
+typedef struct cc65_linedata cc65_linedata;
+struct cc65_linedata {
+ const char* source_name; /* Name of the file */
+ unsigned long source_size; /* Size of file */
+ unsigned long source_mtime; /* Modification time */
+ cc65_line source_line; /* Line number */
+ cc65_addr line_start; /* Start address for this line */
+ cc65_addr line_end; /* End address for this line */
+ const char* output_name; /* Output file */
+ unsigned long output_offs; /* Offset in output file */
+};
+
typedef struct cc65_lineinfo cc65_lineinfo;
struct cc65_lineinfo {
unsigned count; /* Number of data sets that follow */
- struct {
- const char* name; /* Name of the file */
- unsigned long size; /* Size of file */
- unsigned long mtime; /* Modification time */
- cc65_line line; /* Line number */
- cc65_addr start; /* Start address for this line */
- cc65_addr end; /* End address for this line */
- } data[1];
+ cc65_linedata data[1]; /* Data sets, number is dynamic */
};
-/* A list of files with some information */
-typedef struct cc65_filelist cc65_filelist;
-struct cc65_filelist {
- unsigned count; /* Number of data sets that follow */
- struct {
- const char* name; /* Name of the file */
- unsigned long size; /* Size of file */
- unsigned long mtime; /* Modification time */
- } data[1];
+/* Source file information */
+typedef struct cc65_sourcedata cc65_sourcedata;
+struct cc65_sourcedata {
+ const char* source_name; /* Name of the file */
+ unsigned long source_size; /* Size of file */
+ unsigned long source_mtime; /* Modification time */
};
+typedef struct cc65_sourceinfo cc65_sourceinfo;
+struct cc65_sourceinfo {
+ unsigned count; /* Number of data sets that follow */
+ cc65_sourcedata data[1]; /* Data sets, number is dynamic */
+};
+/* Segment information.
+ * Notes:
+ * - output_name may be NULL if the data wasn't written to the output file
+ * (example: bss segment)
+ * - output_offs is invalid if there is no output_name, and may not be of
+ * much use in case of a relocatable output file
+ */
+typedef struct cc65_segmentdata cc65_segmentdata;
+struct cc65_segmentdata {
+ const char* segment_name; /* Name of the segment */
+ cc65_addr segment_start; /* Start address of segment */
+ cc65_addr segment_size; /* Size of segment */
+ const char* output_name; /* Output file this seg was written to */
+ unsigned long output_offs; /* Offset of this seg in output file */
+};
-/* A list of segments with some information */
-typedef struct cc65_segmentlist cc65_segmentlist;
-struct cc65_segmentlist {
+typedef struct cc65_segmentinfo cc65_segmentinfo;
+struct cc65_segmentinfo {
unsigned count; /* Number of data sets that follow */
- struct {
- const char* name; /* Name of the file */
- cc65_addr start; /* Start address of segment */
- cc65_addr end; /* End address of segment */
- } data[1];
+ cc65_segmentdata data[1]; /* Data sets, number is dynamic */
};
void cc65_free_lineinfo (cc65_dbginfo handle, cc65_lineinfo* info);
/* Free line info returned by one of the other functions */
-cc65_filelist* cc65_get_filelist (cc65_dbginfo handle);
-/* Return a list of all files referenced in the debug information */
+cc65_sourceinfo* cc65_get_sourcelist (cc65_dbginfo handle);
+/* Return a list of all source files */
-void cc65_free_filelist (cc65_dbginfo handle, cc65_filelist* list);
-/* free a file list returned by cc65_get_filelist() */
+void cc65_free_sourceinfo (cc65_dbginfo handle, cc65_sourceinfo* info);
+/* Free a source info record */
-cc65_segmentlist* cc65_get_segmentlist (cc65_dbginfo handle);
+cc65_segmentinfo* cc65_get_segmentlist (cc65_dbginfo handle);
/* Return a list of all segments referenced in the debug information */
-void cc65_free_segmentlist (cc65_dbginfo handle, cc65_segmentlist* list);
-/* Free a file list returned by cc65_get_filelist() */
+void cc65_free_segmentinfo (cc65_dbginfo handle, cc65_segmentinfo* info);
+/* Free a segment info record */
{
const char* Input;
cc65_dbginfo Info;
- cc65_filelist* Files;
- cc65_segmentlist* Segments;
+ cc65_sourceinfo* Sources;
+ cc65_segmentinfo* Segments;
cc65_lineinfo* L;
unsigned I;
unsigned long Addr;
printf ("Input file \"%s\" successfully read\n", Input);
/* Output a list of files */
- printf ("Files used in compilation:\n");
- Files = cc65_get_filelist (Info);
- for (I = 0; I < Files->count; ++I) {
- printf (" %s\n", Files->data[I].name);
+ printf ("List of source files:\n");
+ Sources = cc65_get_sourcelist (Info);
+ for (I = 0; I < Sources->count; ++I) {
+ printf (" %s\n", Sources->data[I].source_name);
}
- cc65_free_filelist (Info, Files);
+ cc65_free_sourceinfo (Info, Sources);
/* Output a list of segments */
printf ("Segments processed when linking:\n");
Segments = cc65_get_segmentlist (Info);
for (I = 0; I < Segments->count; ++I) {
- printf (" %-20s $%06lX-$%06lX\n",
- Segments->data[I].name,
- (unsigned long) Segments->data[I].start,
- (unsigned long) Segments->data[I].end);
+ printf (" %-20s $%06lX $%04lX",
+ Segments->data[I].segment_name,
+ (unsigned long) Segments->data[I].segment_start,
+ (unsigned long) Segments->data[I].segment_size);
+ if (Segments->data[I].output_name) {
+ printf (" %-20s $%06lX",
+ Segments->data[I].output_name,
+ Segments->data[I].output_offs);
+ }
+ putchar ('\n');
}
- cc65_free_segmentlist (Info, Segments);
+ cc65_free_segmentinfo (Info, Segments);
/* Check one line */
printf ("Requesting line info for crt0.s(59):\n");
if (L == 0) {
printf (" Not found\n");
} else {
- printf (" Code range is $%04X-$%04X\n", L->data[0].start, L->data[0].end);
+ printf (" Code range is $%04X-$%04X\n",
+ L->data[0].line_start,
+ L->data[0].line_end);
cc65_free_lineinfo (Info, L);
}
-
-
/* Output debug information for all addresses in the complete 6502 address
* space. This is also sort of a benchmark for the search algorithms.
*/
if (I > 0) {
printf (", ");
}
- printf ("%s(%lu)", L->data[I].name,
- (unsigned long) L->data[I].line);
+ printf ("%s(%lu)",
+ L->data[I].source_name,
+ (unsigned long) L->data[I].source_line);
+ if (L->data[I].output_name) {
+ printf (" %s($%06lX)",
+ L->data[I].output_name,
+ L->data[I].output_offs);
+
+ }
}
printf ("\n");
cc65_free_lineinfo (Info, L);