/*****************************************************************************/ /* */ /* dbginfo.h */ /* */ /* cc65 debug info handling */ /* */ /* */ /* */ /* (C) 2010, Ullrich von Bassewitz */ /* Roemerstrasse 52 */ /* D-70794 Filderstadt */ /* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ /* warranty. In no event will the authors be held liable for any damages */ /* arising from the use of this software. */ /* */ /* Permission is granted to anyone to use this software for any purpose, */ /* including commercial applications, and to alter it and redistribute it */ /* freely, subject to the following restrictions: */ /* */ /* 1. The origin of this software must not be misrepresented; you must not */ /* claim that you wrote the original software. If you use this software */ /* in a product, an acknowledgment in the product documentation would be */ /* appreciated but is not required. */ /* 2. Altered source versions must be plainly marked as such, and must not */ /* be misrepresented as being the original software. */ /* 3. This notice may not be removed or altered from any source */ /* distribution. */ /* */ /*****************************************************************************/ #include #include #include #include #include #include #include #include #include "dbginfo.h" /*****************************************************************************/ /* Data */ /*****************************************************************************/ /* Dynamic strings */ typedef struct StrBuf StrBuf; struct StrBuf { char* Buf; /* Pointer to buffer */ unsigned Len; /* Length of the string */ unsigned Allocated; /* Size of allocated memory */ }; /* Initializer for a string buffer */ #define STRBUF_INITIALIZER { 0, 0, 0 } /* An array of pointers that grows if needed */ typedef struct Collection Collection; struct Collection { unsigned Count; /* Number of items in the list */ unsigned Size; /* Size of allocated array */ void** Items; /* Array with dynamic size */ }; /* Initializer for static collections */ #define COLLECTION_INITIALIZER { 0, 0, 0 } /* Data structure containing information from the debug info file. A pointer * to this structure is passed as handle to callers from the outside. */ typedef struct DbgInfo DbgInfo; struct DbgInfo { Collection FileInfos; /* Collection with file infos */ }; /* Input tokens */ typedef enum { TOK_INVALID, /* Invalid token */ TOK_EOF, /* End of file reached */ TOK_INTCON, /* Integer constant */ TOK_STRCON, /* String constant */ TOK_EQUAL, /* = */ TOK_COMMA, /* , */ TOK_MINUS, /* - */ TOK_PLUS, /* + */ TOK_EOL, /* \n */ TOK_ABSOLUTE, /* ABSOLUTE keyword */ TOK_ADDRSIZE, /* ADDRSIZE keyword */ TOK_EQUATE, /* EQUATE keyword */ TOK_FILE, /* FILE keyword */ TOK_LABEL, /* LABEL keyword */ TOK_LINE, /* LINE keyword */ TOK_LONG, /* LONG_keyword */ TOK_MAJOR, /* MAJOR keyword */ TOK_MINOR, /* MINOR keyword */ TOK_MTIME, /* MTIME keyword */ TOK_RANGE, /* RANGE keyword */ TOK_RO, /* RO keyword */ TOK_RW, /* RW keyword */ TOK_SEGMENT, /* SEGMENT keyword */ TOK_SIZE, /* SIZE keyword */ TOK_START, /* START keyword */ TOK_SYM, /* SYM keyword */ TOK_TYPE, /* TYPE keyword */ TOK_VALUE, /* VALUE keyword */ TOK_VERSION, /* VERSION keyword */ TOK_ZEROPAGE, /* ZEROPAGE keyword */ TOK_IDENT, /* To catch unknown keywords */ } Token; /* Data used when parsing the debug info file */ typedef struct InputData InputData; struct InputData { const char* FileName; /* Name of input file */ cc65_line Line; /* Current line number */ unsigned Col; /* Current column number */ cc65_line SLine; /* Line number at start of token */ unsigned SCol; /* Column number at start of token */ unsigned Errors; /* Number of errors */ FILE* F; /* Input file */ int C; /* Input character */ Token Tok; /* Token from input stream */ unsigned long IVal; /* Integer constant */ StrBuf SVal; /* String constant */ cc65_errorfunc Error; /* Function called in case of errors */ unsigned MajorVersion; /* Major version number */ unsigned MinorVersion; /* Minor version number */ Collection LineInfos; /* Line information */ DbgInfo* Info; /* Pointer to debug info */ }; /* Internally used file info struct */ typedef struct FileInfo FileInfo; struct FileInfo { unsigned long Size; /* Size of file */ unsigned long MTime; /* Modification time */ cc65_addr Start; /* Start address of line infos */ cc65_addr End; /* End address of line infos */ Collection LineInfos; /* Line infos for this file */ char FileName[1]; /* Name of file with full path */ }; /* Internally used line info struct */ typedef struct LineInfo LineInfo; struct LineInfo { cc65_addr Start; /* Start of data range */ cc65_addr End; /* End of data range */ cc65_line Line; /* Line number */ FileInfo* FileInfo; /* Pointer to file info */ char FileName[1]; /* Name of file */ }; /*****************************************************************************/ /* Forwards */ /*****************************************************************************/ static void NextToken (InputData* D); /* Read the next token from the input stream */ /*****************************************************************************/ /* Memory allocation */ /*****************************************************************************/ static void* xmalloc (size_t Size) /* Allocate memory, check for out of memory condition. Do some debugging */ { void* P = 0; /* Allow zero sized requests and return NULL in this case */ if (Size) { /* Allocate memory */ P = malloc (Size); /* Check for errors */ assert (P != 0); } /* Return a pointer to the block */ return P; } static void* xrealloc (void* P, size_t Size) /* Reallocate a memory block, check for out of memory */ { /* Reallocate the block */ void* N = realloc (P, Size); /* Check for errors */ assert (N != 0 || Size == 0); /* Return the pointer to the new block */ return N; } static void xfree (void* Block) /* Free the block, do some debugging */ { free (Block); } /*****************************************************************************/ /* Dynamic strings */ /*****************************************************************************/ static void SB_Done (StrBuf* B) /* Free the data of a string buffer (but not the struct itself) */ { if (B->Allocated) { xfree (B->Buf); } } static void SB_Realloc (StrBuf* B, unsigned NewSize) /* Reallocate the string buffer space, make sure at least NewSize bytes are * available. */ { /* Get the current size, use a minimum of 8 bytes */ unsigned NewAllocated = B->Allocated; if (NewAllocated == 0) { NewAllocated = 8; } /* Round up to the next power of two */ while (NewAllocated < NewSize) { NewAllocated *= 2; } /* Reallocate the buffer. Beware: The allocated size may be zero while the * length is not. This means that we have a buffer that wasn't allocated * on the heap. */ if (B->Allocated) { /* Just reallocate the block */ B->Buf = xrealloc (B->Buf, NewAllocated); } else { /* Allocate a new block and copy */ B->Buf = memcpy (xmalloc (NewAllocated), B->Buf, B->Len); } /* Remember the new block size */ B->Allocated = NewAllocated; } static unsigned SB_GetLen (const StrBuf* B) /* Return the length of the buffer contents */ { return B->Len; } static const char* SB_GetConstBuf (const StrBuf* B) /* Return a buffer pointer */ { return B->Buf; } static void SB_Terminate (StrBuf* B) /* Zero terminate the given string buffer. NOTE: The terminating zero is not * accounted for in B->Len, if you want that, you have to use AppendChar! */ { unsigned NewLen = B->Len + 1; if (NewLen > B->Allocated) { SB_Realloc (B, NewLen); } B->Buf[B->Len] = '\0'; } static void SB_Clear (StrBuf* B) /* Clear the string buffer (make it empty) */ { B->Len = 0; } static void SB_AppendChar (StrBuf* B, int C) /* Append a character to a string buffer */ { unsigned NewLen = B->Len + 1; if (NewLen > B->Allocated) { SB_Realloc (B, NewLen); } B->Buf[B->Len] = (char) C; B->Len = NewLen; } /*****************************************************************************/ /* Collections */ /*****************************************************************************/ static Collection* InitCollection (Collection* C) /* Initialize a collection and return it. */ { /* Intialize the fields. */ C->Count = 0; C->Size = 0; C->Items = 0; /* Return the new struct */ return C; } static void DoneCollection (Collection* C) /* Free the data for a collection. This will not free the data contained in * the collection. */ { /* Free the pointer array */ xfree (C->Items); } static unsigned CollCount (const Collection* C) /* Return the number of items in the collection */ { return C->Count; } static void CollGrow (Collection* C, unsigned Size) /* Grow the collection C so it is able to hold Size items without a resize * being necessary. This can be called for performance reasons if the number * of items to be placed in the collection is known in advance. */ { void** NewItems; /* Ignore the call if the collection is already large enough */ if (Size <= C->Size) { return; } /* Grow the collection */ C->Size = Size; NewItems = xmalloc (C->Size * sizeof (void*)); memcpy (NewItems, C->Items, C->Count * sizeof (void*)); xfree (C->Items); C->Items = NewItems; } static void CollInsert (Collection* C, void* Item, unsigned Index) /* Insert the data at the given position in the collection */ { /* Check for invalid indices */ assert (Index <= C->Count); /* Grow the array if necessary */ if (C->Count >= C->Size) { /* Must grow */ CollGrow (C, (C->Size == 0)? 8 : C->Size * 2); } /* Move the existing elements if needed */ if (C->Count != Index) { memmove (C->Items+Index+1, C->Items+Index, (C->Count-Index) * sizeof (void*)); } ++C->Count; /* Store the new item */ C->Items[Index] = Item; } static void CollAppend (Collection* C, void* Item) /* Append an item to the end of the collection */ { /* Insert the item at the end of the current list */ CollInsert (C, Item, C->Count); } static void* CollAt (Collection* C, unsigned Index) /* Return the item at the given index */ { /* Check the index */ assert (Index < C->Count); /* Return the element */ return C->Items[Index]; } static void* CollFirst (Collection* C) /* Return the first item in a collection */ { /* We must have at least one entry */ assert (C->Count > 0); /* Return the element */ return C->Items[0]; } static void* CollLast (Collection* C) /* Return the last item in a collection */ { /* We must have at least one entry */ assert (C->Count > 0); /* Return the element */ return C->Items[C->Count-1]; } static void CollDelete (Collection* C, unsigned Index) /* Remove the item with the given index from the collection. This will not * free the item itself, just the pointer. All items with higher indices * will get moved to a lower position. */ { /* Check the index */ assert (Index < C->Count); /* Remove the item pointer */ --C->Count; memmove (C->Items+Index, C->Items+Index+1, (C->Count-Index) * sizeof (void*)); } static void CollReplace (Collection* C, void* Item, unsigned Index) /* Replace the item at the given position. The old item will not be freed, * just the pointer will get replaced. */ { /* Check the index */ assert (Index < C->Count); /* Replace the item pointer */ C->Items[Index] = Item; } static void CollQuickSort (Collection* C, int Lo, int Hi, int (*Compare) (const void*, const void*)) /* Internal recursive sort function. */ { /* Get a pointer to the items */ void** Items = C->Items; /* Quicksort */ while (Hi > Lo) { int I = Lo + 1; int J = Hi; while (I <= J) { while (I <= J && Compare (Items[Lo], Items[I]) >= 0) { ++I; } while (I <= J && Compare (Items[Lo], Items[J]) < 0) { --J; } if (I <= J) { /* Swap I and J */ void* Tmp = Items[I]; Items[I] = Items[J]; Items[J] = Tmp; ++I; --J; } } if (J != Lo) { /* Swap J and Lo */ void* Tmp = Items[J]; Items[J] = Items[Lo]; Items[Lo] = Tmp; } if (J > (Hi + Lo) / 2) { CollQuickSort (C, J + 1, Hi, Compare); Hi = J - 1; } else { CollQuickSort (C, Lo, J - 1, Compare); Lo = J + 1; } } } void CollSort (Collection* C, int (*Compare) (const void*, const void*)) /* Sort the collection using the given compare function. */ { if (C->Count > 1) { CollQuickSort (C, 0, C->Count-1, Compare); } } /*****************************************************************************/ /* Line info */ /*****************************************************************************/ static LineInfo* NewLineInfo (const StrBuf* FileName) /* Create a new LineInfo struct and return it */ { /* Allocate memory */ LineInfo* L = xmalloc (sizeof (LineInfo) + SB_GetLen (FileName)); /* Initialize it */ L->Start = 0; L->End = 0; L->Line = 0; L->FileInfo = 0; memcpy (L->FileName, SB_GetConstBuf (FileName), SB_GetLen (FileName) + 1); /* Return it */ return L; } static void FreeLineInfo (LineInfo* L) /* Free a LineInfo struct */ { xfree (L); } static LineInfo* PreenLineInfo (LineInfo* L, FileInfo* F) /* Replace the name by file information */ { /* Shrink the LineInfo struct removing the FfileName field */ L = xrealloc (L, sizeof (*L) - 1); /* Set the FileInfo pointer instead */ L->FileInfo = F; /* Return the result */ return L; } static int CompareLineInfo (const void* L, const void* R) /* Helper function to sort line infos in a collection */ { /* Sort by start of range */ if (((const LineInfo*) L)->Start > ((const LineInfo*) R)->Start) { return 1; } else if (((const LineInfo*) L)->Start < ((const LineInfo*) R)->Start) { return -1; } else { return 0; } } /*****************************************************************************/ /* File info */ /*****************************************************************************/ static FileInfo* NewFileInfo (const StrBuf* FileName) /* Create a new FileInfo struct and return it */ { /* Allocate memory */ FileInfo* F = xmalloc (sizeof (FileInfo) + SB_GetLen (FileName)); /* Initialize it */ F->Size = 0; F->MTime = 0; F->Start = ~(cc65_addr)0; F->End = 0; InitCollection (&F->LineInfos); memcpy (F->FileName, SB_GetConstBuf (FileName), SB_GetLen (FileName) + 1); /* Return it */ return F; } static void FreeFileInfo (FileInfo* F) /* Free a FileInfo struct */ { unsigned I; /* Walk through the collection with line infos and delete them */ for (I = 0; I < CollCount (&F->LineInfos); ++I) { FreeLineInfo (CollAt (&F->LineInfos, I)); } DoneCollection (&F->LineInfos); /* Free the file info structure itself */ xfree (F); } static int CompareFileInfo (const void* L, const void* R) /* Helper function to sort file infos in a collection */ { /* Sort by file name */ return strcmp (((const FileInfo*) L)->FileName, ((const FileInfo*) R)->FileName); } /*****************************************************************************/ /* Debug info */ /*****************************************************************************/ static DbgInfo* NewDbgInfo (void) /* Create a new DbgInfo struct and return it */ { /* Allocate memory */ DbgInfo* Info = xmalloc (sizeof (DbgInfo)); /* Initialize it */ InitCollection (&Info->FileInfos); /* Return it */ return Info; } static void FreeDbgInfo (DbgInfo* Info) /* Free a DbgInfo struct */ { unsigned I; /* Free file info */ for (I = 0; I < CollCount (&Info->FileInfos); ++I) { FreeFileInfo (CollAt (&Info->FileInfos, I)); } DoneCollection (&Info->FileInfos); /* Free the structure itself */ xfree (Info); } /*****************************************************************************/ /* Helper functions */ /*****************************************************************************/ static void ParseError (InputData* D, cc65_error_severity Type, const char* Msg, ...) /* Call the user supplied parse error function */ { va_list ap; int MsgSize; cc65_parseerror* E; /* Test-format the error message so we know how much space to allocate */ va_start (ap, Msg); MsgSize = vsnprintf (0, 0, Msg, ap); va_end (ap); /* Allocate memory */ E = xmalloc (sizeof (*E) + MsgSize); /* Write data to E */ E->type = Type; E->name = D->FileName; E->line = D->SLine; E->column = D->SCol; va_start (ap, Msg); vsnprintf (E->errormsg, MsgSize+1, Msg, ap); va_end (ap); /* Call the caller:-) */ D->Error (E); /* Free the data structure */ xfree (E); /* Count errors */ if (Type == CC65_ERROR) { ++D->Errors; } } static void SkipLine (InputData* D) /* Error recovery routine. Skip tokens until EOL or EOF is reached */ { while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) { NextToken (D); } } static void UnexpectedToken (InputData* D) /* Call ParseError with a message about an unexpected input token */ { ParseError (D, CC65_ERROR, "Unexpected input token %d", D->Tok); SkipLine (D); } 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 * keywords, this code may be able to at least ignore them. */ { /* Output a warning */ ParseError (D, CC65_WARNING, "Unknown keyword \"%s\" - skipping", SB_GetConstBuf (&D->SVal)); /* Skip the identifier */ NextToken (D); /* If an equal sign follows, ignore anything up to the next line end * or comma. If a comma or line end follows, we're already done. If * we have none of both, we ignore the remainder of the line. */ if (D->Tok == TOK_EQUAL) { NextToken (D); while (D->Tok != TOK_COMMA && D->Tok != TOK_EOL && D->Tok != TOK_EOF) { NextToken (D); } } else if (D->Tok != TOK_COMMA && D->Tok != TOK_EOL && D->Tok != TOK_EOF) { SkipLine (D); } } /*****************************************************************************/ /* Scanner and parser */ /*****************************************************************************/ static int DigitVal (int C) /* Return the value for a numeric digit. Return -1 if C is invalid */ { if (isdigit (C)) { return C - '0'; } else if (isxdigit (C)) { return toupper (C) - 'A' + 10; } else { return -1; } } static void NextChar (InputData* D) /* Read the next character from the input. Count lines and columns */ { /* Check if we've encountered EOF before */ if (D->C >= 0) { D->C = fgetc (D->F); if (D->C == '\n') { ++D->Line; D->Col = 0; } else { ++D->Col; } } } static void NextToken (InputData* D) /* Read the next token from the input stream */ { static const struct KeywordEntry { const char Keyword[10]; Token Tok; } KeywordTable[] = { { "absolute", TOK_ABSOLUTE }, { "addrsize", TOK_ADDRSIZE }, { "equate", TOK_EQUATE }, { "file", TOK_FILE }, { "label", TOK_LABEL }, { "line", TOK_LINE }, { "long", TOK_LONG }, { "major", TOK_MAJOR }, { "minor", TOK_MINOR }, { "mtime", TOK_MTIME }, { "range", TOK_RANGE }, { "ro", TOK_RO }, { "rw", TOK_RW }, { "segment", TOK_SEGMENT }, { "size", TOK_SIZE }, { "start", TOK_START }, { "sym", TOK_SYM }, { "type", TOK_TYPE }, { "value", TOK_VALUE }, { "version", TOK_VERSION }, { "zeropage", TOK_ZEROPAGE }, }; /* Skip whitespace */ while (D->C == ' ' || D->C == '\t') { NextChar (D); } /* Remember the current position as start of the next token */ D->SLine = D->Line; D->SCol = D->Col; /* Identifier? */ if (D->C == '_' || isalpha (D->C)) { const struct KeywordEntry* Entry; /* Read the identifier */ SB_Clear (&D->SVal); while (D->C == '_' || isalnum (D->C)) { SB_AppendChar (&D->SVal, D->C); NextChar (D); } SB_Terminate (&D->SVal); /* Search the identifier in the keyword table */ Entry = bsearch (SB_GetConstBuf (&D->SVal), KeywordTable, sizeof (KeywordTable) / sizeof (KeywordTable[0]), sizeof (KeywordTable[0]), (int (*)(const void*, const void*)) strcmp); if (Entry == 0) { D->Tok = TOK_IDENT; } else { D->Tok = Entry->Tok; } return; } /* Number? */ if (isdigit (D->C)) { int Base = 10; int Val; if (D->C == '0') { NextChar (D); if (toupper (D->C) == 'X') { NextChar (D); Base = 16; } else { Base = 8; } } else { Base = 10; } D->IVal = 0; while ((Val = DigitVal (D->C)) >= 0 && Val < Base) { D->IVal = D->IVal * Base + Val; NextChar (D); } D->Tok = TOK_INTCON; return; } /* Other characters */ switch (D->C) { case '-': NextChar (D); D->Tok = TOK_MINUS; break; case '+': NextChar (D); D->Tok = TOK_PLUS; break; case ',': NextChar (D); D->Tok = TOK_COMMA; break; case '=': NextChar (D); D->Tok = TOK_EQUAL; break; case '\"': SB_Clear (&D->SVal); NextChar (D); while (1) { if (D->C == '\n' || D->C == EOF) { ParseError (D, CC65_ERROR, "Unterminated string constant"); break; } if (D->C == '\"') { NextChar (D); break; } SB_AppendChar (&D->SVal, D->C); NextChar (D); } SB_Terminate (&D->SVal); D->Tok = TOK_STRCON; break; case '\n': NextChar (D); D->Tok = TOK_EOL; break; case EOF: D->Tok = TOK_EOF; break; default: ParseError (D, CC65_ERROR, "Invalid input character `%c'", D->C); } } static int TokenFollows (InputData* D, Token Tok, const char* Name) /* Check for a comma */ { if (D->Tok != Tok) { ParseError (D, CC65_ERROR, "%s expected", Name); SkipLine (D); return 0; } else { return 1; } } static int IntConstFollows (InputData* D) /* Check for an integer constant */ { return TokenFollows (D, TOK_INTCON, "Integer constant"); } static int StringConstFollows (InputData* D) /* Check for a string literal */ { return TokenFollows (D, TOK_STRCON, "String literal"); } static int Consume (InputData* D, Token Tok, const char* Name) /* Check for a token and consume it. Return true if the token was comsumed, * return false otherwise. */ { if (TokenFollows (D, Tok, Name)) { NextToken (D); return 1; } else { return 0; } } static int ConsumeComma (InputData* D) /* Consume a comma */ { return Consume (D, TOK_COMMA, "','"); } static int ConsumeEqual (InputData* D) /* Consume an equal sign */ { return Consume (D, TOK_EQUAL, "'='"); } static int ConsumeMinus (InputData* D) /* Consume a minus sign */ { return Consume (D, TOK_MINUS, "'-'"); } static void ParseFile (InputData* D) /* Parse a FILE line */ { FileInfo* F; enum { None = 0x00, Size = 0x01, MTime = 0x02 } InfoBits = None; /* Skip the FILE token */ NextToken (D); /* Name follows */ if (!StringConstFollows (D)) { return; } /* Allocate a new file info */ F = NewFileInfo (&D->SVal); /* Skip the file name */ NextToken (D); /* More stuff follows */ while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) { /* Comma follows before next attribute */ if (!ConsumeComma (D)) { goto ErrorExit; } switch (D->Tok) { case TOK_SIZE: NextToken (D); if (!ConsumeEqual (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } F->Size = D->IVal; NextToken (D); InfoBits |= Size; break; case TOK_MTIME: NextToken (D); if (!ConsumeEqual (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } F->MTime = D->IVal; NextToken (D); InfoBits |= MTime; break; case TOK_IDENT: /* Try to skip unknown keywords that may have been added by * a later version. */ UnknownKeyword (D); break; default: UnexpectedToken (D); SkipLine (D); goto ErrorExit; } } /* Check for required information */ if ((InfoBits & Size) == None) { MissingAttribute (D, "size"); goto ErrorExit; } if ((InfoBits & MTime) == None) { MissingAttribute (D, "mtime"); goto ErrorExit; } /* Remember the file info */ CollAppend (&D->Info->FileInfos, F); /* Done */ return; ErrorExit: /* Entry point in case of errors */ FreeFileInfo (F); } static void ParseLine (InputData* D) /* Parse a LINE line */ { LineInfo* L; enum { None = 0x00, Line = 0x01, Range = 0x02 } InfoBits = None; /* Skip the LINE token */ NextToken (D); /* File name follows */ if (!StringConstFollows (D)) { return; } /* Allocate a new line info */ L = NewLineInfo (&D->SVal); /* Skip the file name */ NextToken (D); /* More stuff follows */ while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) { /* Comma follows before next attribute */ if (!ConsumeComma (D)) { goto ErrorExit; } switch (D->Tok) { case TOK_LINE: NextToken (D); if (!ConsumeEqual (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } L->Line = D->IVal; NextToken (D); InfoBits |= Line; break; case TOK_RANGE: NextToken (D); if (!ConsumeEqual (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } L->Start = (cc65_addr) D->IVal; NextToken (D); if (!ConsumeMinus (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } L->End = (cc65_addr) D->IVal; NextToken (D); InfoBits |= Range; break; case TOK_IDENT: /* Try to skip unknown keywords that may have been added by * a later version. */ UnknownKeyword (D); break; default: UnexpectedToken (D); SkipLine (D); goto ErrorExit; } } /* Check for required information */ if ((InfoBits & Line) == None) { MissingAttribute (D, "line"); goto ErrorExit; } if ((InfoBits & Range) == None) { MissingAttribute (D, "range"); goto ErrorExit; } /* Remember the line info */ CollAppend (&D->LineInfos, L); /* Done */ return; ErrorExit: /* Entry point in case of errors */ FreeLineInfo (L); } static void ParseSegment (InputData* D) /* Parse a SEGMENT line */ { /* Skip the SEGMENT token */ NextToken (D); /* ### */ SkipLine (D); } static void ParseSym (InputData* D) /* Parse a SYM line */ { /* Skip the SYM token */ NextToken (D); /* ### */ SkipLine (D); } static void ParseVersion (InputData* D) /* Parse a VERSION line */ { enum { None = 0x00, Major = 0x01, Minor = 0x02 } InfoBits = None; /* Skip the VERSION token */ NextToken (D); /* More stuff follows */ while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) { switch (D->Tok) { case TOK_MAJOR: NextToken (D); if (!ConsumeEqual (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } D->MajorVersion = D->IVal; NextToken (D); InfoBits |= Major; break; case TOK_MINOR: NextToken (D); if (!ConsumeEqual (D)) { goto ErrorExit; } if (!IntConstFollows (D)) { goto ErrorExit; } D->MinorVersion = D->IVal; NextToken (D); InfoBits |= Minor; break; case TOK_IDENT: /* Try to skip unknown keywords that may have been added by * a later version. */ UnknownKeyword (D); break; default: UnexpectedToken (D); SkipLine (D); goto ErrorExit; } /* Comma follows before next attribute */ if (D->Tok == TOK_COMMA) { NextToken (D); } else if (D->Tok == TOK_EOL || D->Tok == TOK_EOF) { break; } else { UnexpectedToken (D); goto ErrorExit; } } /* Check for required information */ if ((InfoBits & Major) == None) { MissingAttribute (D, "major"); goto ErrorExit; } if ((InfoBits & Minor) == None) { MissingAttribute (D, "minor"); goto ErrorExit; } ErrorExit: /* Entry point in case of errors */ return; } /*****************************************************************************/ /* Data processing */ /*****************************************************************************/ static FileInfo* FindFileInfo (InputData* D, const char* FileName) /* Find the FileInfo for a given file name */ { /* Get a pointer to the file info collection */ Collection* FileInfos = &D->Info->FileInfos; /* Do a binary search */ int Lo = 0; int Hi = (int) CollCount (FileInfos) - 1; while (Lo <= Hi) { /* Mid of range */ int Cur = (Lo + Hi) / 2; /* Get item */ FileInfo* CurItem = CollAt (FileInfos, Cur); /* Compare */ int Res = strcmp (CurItem->FileName, FileName); /* Found? */ if (Res < 0) { Lo = Cur + 1; } else if (Res > 0) { Hi = Cur - 1; } else { /* Found! */ return CurItem; } } /* Not found */ return 0; } static void ProcessFileInfo (InputData* D) /* Postprocess file infos */ { /* Get a pointer to the file info collection */ Collection* FileInfos = &D->Info->FileInfos; /* First, sort the file infos, so we can check for duplicates and do * binary search. */ CollSort (FileInfos, CompareFileInfo); /* Cannot work on an empty collection */ if (CollCount (FileInfos) > 0) { /* Walk through the file infos and check for duplicates. If we find * some, warn and remove them, so the file infos are unique after * that step. */ FileInfo* F = CollAt (FileInfos, 0); unsigned I = 1; while (I < CollCount (FileInfos)) { FileInfo* Next = CollAt (FileInfos, I); if (strcmp (F->FileName, Next->FileName) == 0) { /* Warn only if time stamp and/or size is different */ if (F->Size != Next->Size || F->MTime != Next->MTime) { ParseError (D, CC65_WARNING, "Duplicate file entry for \"%s\"", F->FileName); } /* Remove the duplicate entry */ FreeFileInfo (Next); CollDelete (FileInfos, I); } else { /* This one is ok, check the next entry */ F = Next; ++I; } } } } static void ProcessLineInfo (InputData* D) /* Postprocess line infos */ { /* Get pointers to the collections */ Collection* LineInfos = &D->LineInfos; Collection* FileInfos = &D->Info->FileInfos; /* Walk over the line infos and replace the name by a pointer to the * corresponding file info struct. The LineInfo structs will get shrinked * in this process. Add the line info to each file where it is defined. */ unsigned I = 0; while (I < CollCount (LineInfos)) { /* Get LineInfo struct */ LineInfo* L = CollAt (LineInfos, I); /* Find FileInfo that corresponds to name */ FileInfo* F = FindFileInfo (D, L->FileName); /* If we have no corresponding file info, print a warning and remove * the line info. */ if (F == 0) { ParseError (D, CC65_ERROR, "No file info for file \"%s\"", L->FileName); FreeLineInfo (L); CollDelete (LineInfos, I); continue; } /* Shrink the line info struct effectively removing the file name * but set the pointer to the file info now. */ L = PreenLineInfo (L, F); CollReplace (LineInfos, L, I); /* Add this line info to the file where it is defined */ CollAppend (&F->LineInfos, L); /* Next one */ ++I; } /* Walk over all files and sort the line infos for each file by ascending * start address of the range, so we can do a binary search later. */ for (I = 0; I < CollCount (FileInfos); ++I) { /* Get a pointer to this file info */ FileInfo* F = CollAt (FileInfos, I); /* Sort the line infos for this file */ CollSort (&F->LineInfos, CompareLineInfo); /* If there are line info entries, place the first and last address * of into the FileInfo struct itself, so we can rule out a FileInfo * quickly when mapping an address to a line info. */ if (CollCount (&F->LineInfos) > 0) { F->Start = ((const LineInfo*) CollFirst (&F->LineInfos))->Start; F->End = ((const LineInfo*) CollLast (&F->LineInfos))->End; } } } static LineInfo* FindLineInfo (FileInfo* F, cc65_addr Addr) /* Find the LineInfo for a given address */ { Collection* LineInfos; int Hi; int Lo; /* Each file info contains the first and last address for which line * info is available, so we can rule out non matching ones quickly. */ if (Addr < F->Start || Addr > F->End) { return 0; } /* Get a pointer to the line info collection for this file */ LineInfos = &F->LineInfos; /* Do a binary search */ Lo = 0; Hi = (int) CollCount (LineInfos) - 1; while (Lo <= Hi) { /* Mid of range */ int Cur = (Lo + Hi) / 2; /* Get item */ LineInfo* CurItem = CollAt (LineInfos, Cur); /* Found? */ if (Addr < CurItem->Start) { Hi = Cur - 1; } else if (Addr > CurItem->End) { Lo = Cur + 1; } else { /* Found! */ return CurItem; } } /* Not found */ return 0; } /*****************************************************************************/ /* Code */ /*****************************************************************************/ cc65_dbginfo cc65_read_dbginfo (const char* FileName, cc65_errorfunc ErrFunc) /* Parse the debug info file with the given name. On success, the function * will return a pointer to an opaque cc65_dbginfo structure, that must be * passed to the other functions in this module to retrieve information. * errorfunc is called in case of warnings and errors. If the file cannot be * read successfully, NULL is returned. */ { /* Data structure used to control scanning and parsing */ InputData D = { 0, /* Name of input file */ 1, /* Line number */ 0, /* Input file */ 0, /* Line at start of current token */ 0, /* Column at start of current token */ 0, /* Number of errors */ 0, /* Input file */ ' ', /* Input character */ TOK_INVALID, /* Input token */ 0, /* Integer constant */ STRBUF_INITIALIZER, /* String constant */ 0, /* Function called in case of errors */ 0, /* Major version number */ 0, /* Minor version number */ COLLECTION_INITIALIZER, /* Line information */ 0, /* Pointer to debug info */ }; D.FileName = FileName; D.Error = ErrFunc; /* Open the input file */ D.F = fopen (D.FileName, "r"); if (D.F == 0) { /* Cannot open */ ParseError (&D, CC65_ERROR, "Cannot open input file \"%s\": %s", D.FileName, strerror (errno)); return 0; } /* Create a new debug info struct */ D.Info = NewDbgInfo (); /* Prime the pump */ NextToken (&D); /* Parse lines */ while (D.Tok != TOK_EOF) { switch (D.Tok) { case TOK_FILE: ParseFile (&D); break; case TOK_LINE: ParseLine (&D); break; case TOK_SEGMENT: ParseSegment (&D); break; case TOK_SYM: ParseSym (&D); break; case TOK_VERSION: ParseVersion (&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)); 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); } } /* Close the file */ fclose (D.F); /* Free memory allocated for SVal */ SB_Done (&D.SVal); /* In case of errors, delete the debug info already allocated and * return NULL */ if (D.Errors > 0) { /* Free allocated stuff */ unsigned I; for (I = 0; I < CollCount (&D.LineInfos); ++I) { FreeLineInfo (CollAt (&D.LineInfos, I)); } DoneCollection (&D.LineInfos); FreeDbgInfo (D.Info); return 0; } /* We do now have all the information from the file. Do postprocessing. */ ProcessFileInfo (&D); ProcessLineInfo (&D); /* Free the collection that contained the line info */ DoneCollection (&D.LineInfos); /* Return the debug info struct that was created */ return D.Info; } void cc65_free_dbginfo (cc65_dbginfo Handle) /* Free debug information read from a file */ { if (Handle) { FreeDbgInfo (Handle); } } cc65_lineinfo* cc65_get_lineinfo (cc65_dbginfo Handle, unsigned long Addr) /* Return line information for the given address. The function returns 0 * if no line information was found. */ { unsigned I; Collection* FileInfos; cc65_lineinfo* D = 0; /* We will place a list of line infos in a collection */ Collection LineInfos = COLLECTION_INITIALIZER; /* Check the parameter */ assert (Handle != 0); /* Walk over all files and search for matching line infos */ FileInfos = &((DbgInfo*) Handle)->FileInfos; for (I = 0; I < CollCount (FileInfos); ++I) { /* Check if the file contains line info for this address */ LineInfo* L = FindLineInfo (CollAt (FileInfos, I), Addr); if (L != 0) { CollAppend (&LineInfos, L); } } /* Do we have line infos? */ if (CollCount (&LineInfos) > 0) { /* Prepare the struct we will return to the caller */ D = xmalloc (sizeof (*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->FileInfo->FileName; D->data[I].size = L->FileInfo->Size; D->data[I].mtime = L->FileInfo->MTime; D->data[I].line = L->Line; D->data[I].start = L->Start; D->data[I].end = L->End; } } /* Free the line info collection */ DoneCollection (&LineInfos); /* Return the struct we've created */ return D; } void cc65_free_lineinfo (cc65_dbginfo Handle, cc65_lineinfo* Info) /* Free line info returned by cc65_get_lineinfo() */ { /* Just for completeness, check the handle */ assert (Handle != 0); /* Just free the memory */ xfree (Info); }