/*****************************************************************************/ /* */ /* listing.c */ /* */ /* Listing support for the ca65 crossassembler */ /* */ /* */ /* */ /* (C) 2000-2011, 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 /* common */ #include "check.h" #include "fname.h" #include "fragdefs.h" #include "strbuf.h" #include "version.h" #include "xmalloc.h" /* ca65 */ #include "error.h" #include "filetab.h" #include "global.h" #include "listing.h" #include "segment.h" /*****************************************************************************/ /* Data */ /*****************************************************************************/ /* Single linked list of lines */ ListLine* LineList = 0; /* List of listing lines */ ListLine* LineCur = 0; /* Current listing line */ ListLine* LineLast = 0; /* Last (current) listing line */ /* Page and other formatting */ int PageLength = -1; /* Length of a listing page */ static unsigned PageNumber = 1; /* Current listing page number */ static int PageLines = 0; /* Current line on page */ static unsigned ListBytes = 12; /* Number of bytes to list for one line */ /* Switch the listing on/off */ static int ListingEnabled = 1; /* Enabled if > 0 */ /*****************************************************************************/ /* Code */ /*****************************************************************************/ void NewListingLine (const StrBuf* Line, unsigned char File, unsigned char Depth) /* Create a new ListLine struct and insert it */ { /* Store only if listing is enabled */ if (SB_GetLen (&ListingName) > 0) { ListLine* L; /* Get the length of the line */ unsigned Len = SB_GetLen (Line); /* Ignore trailing newlines */ while (Len > 0 && SB_AtUnchecked (Line, Len-1) == '\n') { --Len; } /* Allocate memory */ L = xmalloc (sizeof (ListLine) + Len); /* Initialize the fields. */ L->Next = 0; L->FragList = 0; L->FragLast = 0; L->PC = GetPC (); L->Reloc = GetRelocMode (); L->File = File; L->Depth = Depth; L->Output = (ListingEnabled > 0); L->ListBytes = (unsigned char) ListBytes; memcpy (L->Line, SB_GetConstBuf (Line), Len); L->Line[Len] = '\0'; /* Insert the line into the list of lines */ if (LineList == 0) { LineList = L; } else { LineLast->Next = L; } LineLast = L; } } void EnableListing (void) /* Enable output of lines to the listing */ { if (SB_GetLen (&ListingName) > 0) { /* If we're about to enable the listing, do this for the current line ** also, so we will see the source line that did this. */ if (ListingEnabled++ == 0) { LineCur->Output = 1; } } } void DisableListing (void) /* Disable output of lines to the listing */ { if (SB_GetLen (&ListingName) > 0) { if (ListingEnabled == 0) { /* Cannot switch the listing off once more */ Error ("Counter underflow"); } else { --ListingEnabled; } } } void SetListBytes (int Bytes) /* Set the maximum number of bytes listed for one line */ { if (Bytes < 0) { Bytes = 0; /* Encode "unlimited" as zero */ } ListBytes = Bytes; } void InitListingLine (void) /* Initialize the current listing line */ { if (SB_GetLen (&ListingName) > 0) { /* Make the last loaded line the current line */ /* ###### This code is a hack! We really need to do it right -- ** as soon as we know how. :-( */ if (LineCur && LineCur->Next && LineCur->Next != LineLast) { ListLine* L = LineCur; do { L = L->Next; /* Set the values for this line */ CHECK (L != 0); L->PC = GetPC (); L->Reloc = GetRelocMode (); L->Output = (ListingEnabled > 0); L->ListBytes = (unsigned char) ListBytes; } while (L->Next != LineLast); } LineCur = LineLast; /* Set the values for this line */ CHECK (LineCur != 0); LineCur->PC = GetPC (); LineCur->Reloc = GetRelocMode (); LineCur->Output = (ListingEnabled > 0); LineCur->ListBytes = (unsigned char) ListBytes; } } static char* AddHex (char* S, unsigned Val) /* Add a hex byte in ASCII to the given string and return the new pointer */ { static const char HexTab [16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; *S++ = HexTab [(Val >> 4) & 0x0F]; *S++ = HexTab [Val & 0x0F]; return S; } static void PrintPageHeader (FILE* F, const ListLine* L) /* Print the header for a new page. It is assumed that the given line is the ** last line of the previous page. */ { /* Gte a pointer to the current input file */ const StrBuf* CurFile = GetFileName (L->File); /* Print the header on the new page */ fprintf (F, "ca65 V%s\n" "Main file : %s\n" "Current file: %.*s\n" "\n", GetVersionAsString (), InFile, (int) SB_GetLen (CurFile), SB_GetConstBuf (CurFile)); /* Count pages, reset lines */ ++PageNumber; PageLines = 4; } static void PrintLine (FILE* F, const char* Header, const char* Line, const ListLine* L) /* Print one line to the listing file, adding a newline and counting lines */ { /* Print the given line */ fprintf (F, "%s%s\n", Header, Line); /* Increment the current line */ ++PageLines; /* Switch to a new page if needed. Do not switch, if the current line is ** the last one, to avoid pages that consist of just the header. */ if (PageLength > 0 && PageLines >= PageLength && L->Next != 0) { /* Do a formfeed */ putc ('\f', F); /* Print the header on the new page */ PrintPageHeader (F, L); } } static char* AddMult (char* S, char C, unsigned Count) /* Add multiple instances of character C to S, return updated S. */ { memset (S, C, Count); return S + Count; } static char* MakeLineHeader (char* H, const ListLine* L) /* Prepare the line header */ { char Mode; char Depth; /* Setup the PC mode */ Mode = (L->Reloc)? 'r' : ' '; /* Set up the include depth */ Depth = (L->Depth < 10)? L->Depth + '0' : '+'; /* Format the line */ sprintf (H, "%06lX%c %c", L->PC, Mode, Depth); memset (H+9, ' ', LINE_HEADER_LEN-9); /* Return the buffer */ return H; } void CreateListing (void) /* Create the listing */ { FILE* F; Fragment* Frag; ListLine* L; char HeaderBuf [LINE_HEADER_LEN+1]; /* Open the real listing file */ F = fopen (SB_GetConstBuf (&ListingName), "w"); if (F == 0) { Fatal ("Cannot open listing file '%s': %s", SB_GetConstBuf (&ListingName), strerror (errno)); } /* Reset variables, print the header for the first page */ PageNumber = 0; PrintPageHeader (F, LineList); /* Terminate the header buffer. The last byte will never get overwritten */ HeaderBuf [LINE_HEADER_LEN] = '\0'; /* Walk through all listing lines */ L = LineList; while (L) { char* Buf; char* B; unsigned Count; unsigned I; char* Line; /* If we should not output this line, go to the next */ if (L->Output == 0) { L = L->Next; continue; } /* If we don't have a fragment list for this line, things are easy */ if (L->FragList == 0) { PrintLine (F, MakeLineHeader (HeaderBuf, L), L->Line, L); L = L->Next; continue; } /* Count the number of bytes in the complete fragment list */ Count = 0; Frag = L->FragList; while (Frag) { Count += Frag->Len; Frag = Frag->LineList; } /* Allocate memory for the given number of bytes */ Buf = xmalloc (Count*2+1); /* Copy an ASCII representation of the bytes into the buffer */ B = Buf; Frag = L->FragList; while (Frag) { /* Write data depending on the type */ switch (Frag->Type) { case FRAG_LITERAL: for (I = 0; I < Frag->Len; ++I) { B = AddHex (B, Frag->V.Data[I]); } break; case FRAG_EXPR: case FRAG_SEXPR: B = AddMult (B, 'r', Frag->Len*2); break; case FRAG_FILL: B = AddMult (B, 'x', Frag->Len*2); break; default: Internal ("Invalid fragment type: %u", Frag->Type); } /* Next fragment */ Frag = Frag->LineList; } /* Limit the number of bytes actually printed */ if (L->ListBytes != 0) { /* Not unlimited */ if (Count > L->ListBytes) { Count = L->ListBytes; } } /* Output the data. The format of a listing line is: ** ** PPPPPPm I 11 22 33 44 ** ** where ** ** PPPPPP is the PC ** m is the mode ('r' or empty) ** I is the include level ** 11 .. are code or data bytes */ Line = L->Line; B = Buf; while (Count) { unsigned Chunk; char* P; /* Prepare the line header */ MakeLineHeader (HeaderBuf, L); /* Get the number of bytes for the next line */ Chunk = Count; if (Chunk > 4) { Chunk = 4; } Count -= Chunk; /* Increment the program counter. Since we don't need the PC stored ** in the LineList object for anything else, just increment this ** variable. */ L->PC += Chunk; /* Copy the bytes into the line */ P = HeaderBuf + 11; for (I = 0; I < Chunk; ++I) { *P++ = *B++; *P++ = *B++; *P++ = ' '; } /* Output this line */ PrintLine (F, HeaderBuf, Line, L); /* Don't output a line twice */ Line = ""; } /* Delete the temporary buffer */ xfree (Buf); /* Next line */ L = L->Next; } /* Close the listing file */ (void) fclose (F); }