/*****************************************************************************/
/* */
-/* dbginfo.h */
+/* dbginfo.c */
/* */
/* cc65 debug info handling */
/* */
unsigned Id; /* Parent symbol if any */
SymInfo* Info; /* Pointer to parent symbol if any */
} Parent;
+ Collection* ImportList; /* List of imports if this is an export */
char Name[1]; /* Name of symbol */
};
-/*****************************************************************************/
-/* Id lists */
-/*****************************************************************************/
-
-
-
-static void init_cc65_idlist (cc65_idlist* L, unsigned Count)
-/* Initialize an idlist with the given count. The count field in the list
- * will be set on return and memory for the list is allocated.
- */
-{
- L->count = Count;
- if (Count == 0) {
- L->ids = 0;
- } else {
- L->ids = xmalloc (Count * sizeof (L->ids[0]));
- }
-}
-
-
-
/*****************************************************************************/
/* File info */
/*****************************************************************************/
static void CopyLineInfo (cc65_linedata* D, const LineInfo* L)
/* Copy data from a LineInfo struct to a cc65_linedata struct */
{
- unsigned I;
-
D->line_id = L->Id;
D->source_id = L->File.Info->Id;
D->source_line = L->Line;
D->line_type = L->Type;
D->count = L->Count;
- init_cc65_idlist (&D->span_list, CollCount (&L->SpanInfoList));
- for (I = 0; I < CollCount (&L->SpanInfoList); ++I) {
- const SpanInfo* S = CollConstAt (&L->SpanInfoList, I);
- D->span_list.ids[I] = S->Id;
- }
}
/* Allocate memory */
SymInfo* S = xmalloc (sizeof (SymInfo) + SB_GetLen (Name));
- /* Initialize the name */
+ /* Initialize it as necessary */
+ S->ImportList = 0;
memcpy (S->Name, SB_GetConstBuf (Name), SB_GetLen (Name) + 1);
/* Return it */
static void FreeSymInfo (SymInfo* S)
/* Free a SymInfo struct */
{
+ CollFree (S->ImportList);
xfree (S);
}
unsigned ScopeId = CC65_INV_ID;
unsigned SegId = CC65_INV_ID;
cc65_size Size = 0;
- cc65_symbol_type Type = CC65_SYM_EQUATE;
+ cc65_symbol_type Type = CC65_SYM_EQUATE;
long Value = 0;
SymInfo* S;
static void ProcessLineInfo (InputData* D)
/* Postprocess line infos */
{
- unsigned I;
+ unsigned I, J;
/* Get pointers to the collections */
Collection* LineInfos = &D->Info->LineInfoById;
/* Walk over the line infos and replace the id numbers of file and segment
* with pointers to the actual structs. Add the line info to each file
- * where it is defined.
+ * where it is defined. Resolve the spans and add backpointers to the
+ * spans.
*/
for (I = 0; I < CollCount (LineInfos); ++I) {
CollAppend (&L->File.Info->LineInfoByLine, L);
}
- /* Next one */
- ++I;
+ /* Resolve the spans ids */
+ for (J = 0; J < CollCount (&L->SpanInfoList); ++J) {
+
+ /* Get the id of this span */
+ unsigned SpanId = CollIdAt (&L->SpanInfoList, J);
+ if (SpanId >= CollCount (&D->Info->SpanInfoById)) {
+ ParseError (D,
+ CC65_ERROR,
+ "Invalid span id %u for line with id %u",
+ SpanId, L->Id);
+ CollReplace (&L->SpanInfoList, 0, J);
+ } else {
+
+ /* Get a pointer to the span */
+ SpanInfo* SP = CollAt (&D->Info->SpanInfoById, SpanId);
+
+ /* Replace the id by the pointer */
+ CollReplace (&L->SpanInfoList, SP, J);
+
+ /* Insert a backpointer into the span */
+ if (SP->LineInfoList == 0) {
+ SP->LineInfoList = CollNew ();
+ }
+ CollAppend (SP->LineInfoList, L);
+ }
+ }
}
/* Walk over all files and sort the line infos for each file so we can
S->Exp.Info = 0;
} else {
S->Exp.Info = CollAt (&D->Info->SymInfoById, S->Exp.Id);
+
+ /* Add a backpointer, so the export knows its imports */
+ if (S->Exp.Info->ImportList == 0) {
+ S->Exp.Info->ImportList = CollNew ();
+ }
+ CollAppend (S->Exp.Info->ImportList, S);
}
/* Resolve segment */
+const cc65_spaninfo* cc65_spaninfo_byline (cc65_dbginfo Handle, unsigned LineId)
+/* Return span information for the given source line. The function returns NULL
+ * if the line id is invalid, otherwise the spans for this line (possibly zero).
+ */
+{
+ DbgInfo* Info;
+ LineInfo* L;
+ cc65_spaninfo* D;
+ unsigned I;
+
+ /* Check the parameter */
+ assert (Handle != 0);
+
+ /* The handle is actually a pointer to a debug info struct */
+ Info = (DbgInfo*) Handle;
+
+ /* Check if the line id is valid */
+ if (LineId >= CollCount (&Info->LineInfoById)) {
+ return 0;
+ }
+
+ /* Get the line with this id */
+ L = CollAt (&Info->LineInfoById, LineId);
+
+ /* Allocate memory for the data structure returned to the caller */
+ D = new_cc65_spaninfo (CollCount (&L->SpanInfoList));
+
+ /* Fill in the data */
+ for (I = 0; I < CollCount (&L->SpanInfoList); ++I) {
+ /* Copy the data */
+ CopySpanInfo (D->data + I, CollConstAt (&L->SpanInfoList, I));
+ }
+
+ /* Return the result */
+ return D;
+}
+
+
+
+const cc65_spaninfo* cc65_spaninfo_byscope (cc65_dbginfo Handle, unsigned ScopeId)
+/* Return span information for the given scope. The function returns NULL if
+ * the scope id is invalid, otherwise the spans for this scope (possibly zero).
+ */
+{
+ DbgInfo* Info;
+ ScopeInfo* S;
+ cc65_spaninfo* D;
+ unsigned I;
+
+ /* Check the parameter */
+ assert (Handle != 0);
+
+ /* The handle is actually a pointer to a debug info struct */
+ Info = (DbgInfo*) Handle;
+
+ /* Check if the scope id is valid */
+ if (ScopeId >= CollCount (&Info->ScopeInfoById)) {
+ return 0;
+ }
+
+ /* Get the scope with this id */
+ S = CollAt (&Info->ScopeInfoById, ScopeId);
+
+ /* Allocate memory for the data structure returned to the caller */
+ D = new_cc65_spaninfo (CollCount (&S->SpanInfoList));
+
+ /* Fill in the data */
+ for (I = 0; I < CollCount (&S->SpanInfoList); ++I) {
+ /* Copy the data */
+ CopySpanInfo (D->data + I, CollConstAt (&S->SpanInfoList, I));
+ }
+
+ /* Return the result */
+ return D;
+}
+
+
+
void cc65_free_spaninfo (cc65_dbginfo Handle, const cc65_spaninfo* Info)
/* Free a span info record */
{
break;
}
- /* Ignore non-labels */
- if (Item->Type != CC65_SYM_LABEL) {
+ /* Ignore non-labels and imports */
+ if (Item->Type != CC65_SYM_LABEL || Item->Exp.Info != 0) {
continue;
}
/* A value that is used to mark invalid ids */
#define CC65_INV_ID (~0U)
-/* A structure that is used to store a list of ids */
-typedef struct cc65_idlist cc65_idlist;
-struct cc65_idlist {
- unsigned count; /* Number of elements */
- unsigned* ids; /* List of ids, number is dynamic */
-};
-
/*****************************************************************************/
};
/* Function that is called in case of parse errors */
-typedef void (*cc65_errorfunc) (const struct cc65_parseerror*);
+typedef void (*cc65_errorfunc) (const cc65_parseerror*);
/* Pointer to an opaque data structure containing information from the debug
* info file. Actually a handle to the data in the file.
cc65_line source_line; /* Line number */
cc65_line_type line_type; /* Type of line */
unsigned count; /* Nesting counter for macros */
- cc65_idlist span_list; /* List of spans for this line */
};
typedef struct cc65_lineinfo cc65_lineinfo;
* if no spans were found for this address.
*/
+const cc65_spaninfo* cc65_spaninfo_byline (cc65_dbginfo handle,
+ unsigned line_id);
+/* Return span information for the given source line. The function returns NULL
+ * if the line id is invalid, otherwise the spans for this line (possibly zero).
+ */
+
+const cc65_spaninfo* cc65_spaninfo_byscope (cc65_dbginfo handle,
+ unsigned scope_id);
+/* Return span information for the given scope. The function returns NULL if
+ * the scope id is invalid, otherwise the spans for this scope (possibly zero).
+ */
+
void cc65_free_spaninfo (cc65_dbginfo handle, const cc65_spaninfo* info);
/* Free a span info record */
} cc65_symbol_type;
/* Notes:
- * - If the symbol is segment relative, the segment id gives segment
+ * - If the symbol is segment relative, the segment id gives segment
* information, otherwise it contains CC65_INV_ID.
* - If export_id is valid (not CC65_INV_ID), the symbol is an import and
* export_id allows to retrieve the corresponding export. The fields
--- /dev/null
+/*****************************************************************************/
+/* */
+/* dbgsh.c */
+/* */
+/* debug info test shell */
+/* */
+/* */
+/* */
+/* (C) 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <assert.h>
+#include <errno.h>
+
+/* common */
+#include "attrib.h"
+#include "chartype.h"
+#include "coll.h"
+
+/* dbginfo */
+#include "dbginfo.h"
+
+
+
+/*****************************************************************************/
+/* Data */
+/*****************************************************************************/
+
+
+
+/* Terminate flag - end program when set to true */
+static int Terminate = 0;
+
+/* The debug file data */
+static cc65_dbginfo Info = 0;
+
+/* Error and warning counters */
+static unsigned FileErrors = 0;
+static unsigned FileWarnings = 0;
+
+/* Structure that contains a command description */
+typedef struct CmdEntry CmdEntry;
+struct CmdEntry {
+ char Cmd[12];
+ int ArgCount;
+ void (*Func) (Collection*);
+};
+
+
+
+/*****************************************************************************/
+/* Debug file handling */
+/*****************************************************************************/
+
+
+
+static void CloseFile (void)
+/* Close the debug info file */
+{
+ if (Info) {
+ cc65_free_dbginfo (Info);
+ Info = 0;
+ }
+}
+
+
+
+static int FileIsOpen (void)
+/* Return true if the file is open and has loaded without errors: If not,
+ * print an error message and return false.
+ */
+{
+ /* File open? */
+ if (Info == 0) {
+ printf ("No debug info file\n");
+ return 0;
+ }
+
+ /* Errors on load? */
+ if (FileErrors > 0) {
+ printf ("File had load errors!\n");
+ return 0;
+ }
+
+ /* Warnings on load? */
+ if (FileWarnings > 0) {
+ printf ("Beware - file had load warnings!\n");
+ }
+
+ /* Ok */
+ return 1;
+}
+
+
+
+/*****************************************************************************/
+/* Helpers */
+/*****************************************************************************/
+
+
+
+static void FileError (const cc65_parseerror* Info)
+/* Callback function - is called in case of errors */
+{
+ /* Output a message */
+ printf ("%s:%s(%lu): %s\n",
+ Info->type? "Error" : "Warning",
+ Info->name,
+ (unsigned long) Info->line,
+ Info->errormsg);
+
+ /* Bump the counters */
+ switch (Info->type) {
+ case CC65_WARNING: ++FileWarnings; break;
+ default: ++FileErrors; break;
+ }
+}
+
+
+
+/*****************************************************************************/
+/* Code */
+/*****************************************************************************/
+
+
+
+static void CmdOpen (Collection* Args)
+/* Open a debug info file */
+{
+ /* Argument is file name */
+ if (CollCount (Args) != 2) {
+ printf ("Command requires exactly one argument\n");
+ return;
+ }
+
+ /* Close an open file */
+ CloseFile ();
+
+ /* Clear the counters */
+ FileErrors = 0;
+ FileWarnings = 0;
+
+ /* Open the debug info file */
+ Info = cc65_read_dbginfo (CollAt (Args, 1), FileError);
+}
+
+
+
+static void CmdClose (Collection* Args attribute ((unused)))
+/* Close a debug info file */
+{
+ CloseFile ();
+}
+
+
+
+static void CmdQuit (Collection* Args attribute ((unused)))
+/* Terminate the application */
+{
+ Terminate = 1;
+}
+
+
+
+static int Parse (char* CmdLine, Collection* Args)
+/* Parse the command line and store the arguments in Args. Return true if ok,
+ * false on error.
+ */
+{
+ char* End;
+
+ /* Clear the collection */
+ CollDeleteAll (Args);
+
+ /* Parse the command line */
+ while (1) {
+
+ /* Break out on end of line */
+ if (*CmdLine == '\0') {
+ break;
+ }
+
+ /* Search for start of next command */
+ if (IsSpace (*CmdLine)) {
+ ++CmdLine;
+ continue;
+ }
+
+ /* Allow double quotes to terminate a command */
+ if (*CmdLine == '\"' || *CmdLine == '\'') {
+ char Term = *CmdLine++;
+ End = CmdLine;
+ while (*End != Term) {
+ if (*End == '\0') {
+ fputs ("Unterminated argument\n", stdout);
+ return 0;
+ }
+ ++End;
+ }
+ *End++ = '\0';
+ } else {
+ End = CmdLine;
+ while (!IsSpace (*End)) {
+ if (*End == '\0') {
+ fputs ("Unterminated argument\n", stdout);
+ return 0;
+ }
+ ++End;
+ }
+ *End++ = '\0';
+ }
+ CollAppend (Args, CmdLine);
+ CmdLine = End;
+ }
+
+ /* Ok */
+ return 1;
+}
+
+
+
+static const CmdEntry* FindCmd (const char* Cmd)
+/* Search for a command */
+{
+ static const CmdEntry CmdTab[] = {
+ { "close", 0, CmdClose },
+ { "open", 1, CmdOpen },
+ { "quit", 0, CmdQuit },
+ };
+
+ unsigned I;
+ for (I = 0; I < sizeof (CmdTab) / sizeof (CmdTab[0]); ++I) {
+ if (strcmp (Cmd, CmdTab[I].Cmd) == 0) {
+ return CmdTab + I;
+ }
+ }
+ return 0;
+}
+
+
+
+int main (void)
+/* Main program */
+{
+ char Input[256];
+ Collection Args = STATIC_COLLECTION_INITIALIZER;
+
+ const char* Cmd;
+ const CmdEntry* E;
+ while (!Terminate) {
+
+ /* Output a prompt, then read the input */
+ fputs ("dbgsh> ", stdout);
+ fflush (stdout);
+ if (fgets (Input, sizeof (Input), stdin) == 0) {
+ fputs ("(EOF)\n", stdout);
+ break;
+ }
+
+ /* Parse the command line */
+ if (Parse (Input, &Args) == 0 || CollCount (&Args) == 0) {
+ continue;
+ }
+
+ /* Search for the command, then execute it */
+ Cmd = CollAt (&Args, 0);
+ E = FindCmd (Cmd);
+ if (E == 0) {
+ printf ("No such command: %s\n", Cmd);
+ } else {
+ E->Func (&Args);
+ }
+ }
+
+ /* Free arguments */
+ DoneCollection (&Args);
+ return 0;
+}
+
+
+
+++ /dev/null
-/*****************************************************************************/
-/* */
-/* dbgtest.c */
-/* */
-/* Test file for the cc65 dbginfo module */
-/* */
-/* */
-/* */
-/* (C) 2010-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 <stdio.h>
-#include <stdlib.h>
-#include "dbginfo.h"
-
-
-
-static cc65_dbginfo Info;
-
-
-
-static void ErrorFunc (const struct cc65_parseerror* E)
-/* Handle errors or warnings that occur while parsing a debug file */
-{
- fprintf (stderr,
- "%s:%s(%lu): %s\n",
- E->type? "Error" : "Warning",
- E->name,
- (unsigned long) E->line,
- E->errormsg);
-}
-
-
-
-static void Usage (void)
-/* Print usage information and exit */
-{
- printf ("Usage: dbgtest debug-file\n");
- exit (1);
-}
-
-
-
-static void PrintSourceData (const cc65_sourcedata* D)
-/* Print the data for one source file */
-{
- printf (" %s\n", D->source_name);
-}
-
-
-
-static void PrintSegmentData (const cc65_segmentdata* D)
-/* Print the data for one segment */
-{
- printf (" %-20s $%06lX $%04lX",
- D->segment_name,
- (unsigned long) D->segment_start,
- (unsigned long) D->segment_size);
- if (D->output_name) {
- printf (" %-20s $%06lX", D->output_name, D->output_offs);
- }
- putchar ('\n');
-}
-
-
-
-static void PrintLineData (const cc65_linedata* D)
-/* Print the data for one source line */
-{
- printf (" file %u", D->source_id);
- switch (D->line_type) {
- case CC65_LINE_ASM:
- printf (": Assembler source");
- break;
- case CC65_LINE_EXT:
- printf (": Externally supplied");
- break;
- case CC65_LINE_MACRO:
- printf (": Macro expansion (%u)", D->count);
- break;
- default:
- printf (": Unknown type %u (%u)", D->line_type, D->count);
- break;
- }
- putchar ('\n');
-}
-
-
-
-static void PrintSymbolData (const cc65_symboldata* D)
-/* Print the data for one symbol */
-{
- char Segment[256] = { 0 }; /* Needs dynamic alloc ### */
- if (D->segment_id != CC65_INV_ID) {
- const cc65_segmentinfo* I = cc65_segmentinfo_byid (Info, D->segment_id);
- if (I && I->count == 1) {
- sprintf (Segment, "segment=%s,", I->data[0].segment_name);
- cc65_free_segmentinfo (Info, I);
- }
- }
-
-
- printf (" %-20s = %04lX (%ssize=%u)\n",
- D->symbol_name,
- D->symbol_value,
- Segment,
- D->symbol_size);
-}
-
-
-
-static void PrintSourceInfo (const cc65_sourceinfo* Sources)
-/* Output the list of source files */
-{
- unsigned I;
- if (Sources) {
- for (I = 0; I < Sources->count; ++I) {
- PrintSourceData (Sources->data + I);
- }
- }
-}
-
-
-
-static void PrintSegmentInfo (const cc65_segmentinfo* Segments)
-/* Output the list of segments */
-{
- unsigned I;
- if (Segments) {
- for (I = 0; I < Segments->count; ++I) {
- PrintSegmentData (Segments->data + I);
- }
- }
-}
-
-
-
-static void PrintLineInfo (const cc65_lineinfo* Info)
-/* Print a list of line infos */
-{
- unsigned I;
- if (Info) {
- for (I = 0; I < Info->count; ++I) {
- PrintLineData (Info->data + I);
- }
- }
-}
-
-
-
-static void PrintSymbolInfo (const cc65_symbolinfo* Symbols)
-/* Print a list of symbol infos */
-{
- unsigned I;
- if (Symbols) {
- for (I = 0; I < Symbols->count; ++I) {
- PrintSymbolData (Symbols->data + I);
- }
- }
-}
-
-
-
-int main (int argc, char** argv)
-{
- const char* Input;
- const cc65_sourceinfo* Sources;
- const cc65_segmentinfo* Segments;
- const cc65_lineinfo* Lines;
- const cc65_symbolinfo* Symbols;
- cc65_addr Addr;
-
-
- /* Input file is argument */
- if (argc != 2) {
- Usage ();
- }
- Input = argv[1];
-
- /* Read the file */
- Info = cc65_read_dbginfo (Input, ErrorFunc);
- if (Info == 0) {
- fprintf (stderr, "Error reading input file - aborting\n");
- return 1;
- }
- printf ("Input file \"%s\" successfully read\n", Input);
-
- /* Output a list of files */
- printf ("List of source files:\n");
- Sources = cc65_get_sourcelist (Info);
- PrintSourceInfo (Sources);
- cc65_free_sourceinfo (Info, Sources);
-
- /* Output a list of segments */
- printf ("Segments processed when linking:\n");
- Segments = cc65_get_segmentlist (Info);
- PrintSegmentInfo (Segments);
- cc65_free_segmentinfo (Info, Segments);
-
-#if 0
- /* Check one line */
- printf ("Requesting line info for crt0.s(59):\n");
- Lines = cc65_lineinfo_byname (Info, "crt0.s", 59);
- if (Lines == 0) {
- printf (" Not found\n");
- } else {
- PrintLineInfo (Lines);
- cc65_free_lineinfo (Info, Lines);
- }
-
- /* Output debug information for all addresses in the complete 6502 address
- * space. This is also sort of a benchmark for the search algorithms.
- */
- printf ("Line info:\n");
- for (Addr = 0; Addr < 0x10000; ++Addr) {
- Lines = cc65_lineinfo_byaddr (Info, Addr);
- if (Lines) {
- printf (" $%04lX:\n", (unsigned long) Addr);
- PrintLineInfo (Lines);
- cc65_free_lineinfo (Info, Lines);
- }
- }
-#endif
- /* Check for address of main */
- printf ("Requesting address of _main:\n");
- Symbols = cc65_symbol_byname (Info, "_main");
- if (Symbols == 0) {
- printf (" Not found\n");
- Addr = 0x800;
- } else {
- PrintSymbolInfo (Symbols);
- Addr = Symbols->data[0].symbol_value;
- cc65_free_symbolinfo (Info, Symbols);
- }
-
- /* Print symbols for the next $100 bytes starting from main (or 0x800) */
- printf ("Requesting labels for $%04lX-$%04lX:\n",
- (unsigned long) Addr, (unsigned long) Addr + 0xFF);
- Symbols = cc65_symbol_inrange (Info, Addr, Addr + 0xFF);
- if (Symbols == 0) {
- printf (" None found\n");
- } else {
- PrintSymbolInfo (Symbols);
- cc65_free_symbolinfo (Info, Symbols);
- }
-
- /* Free the debug info */
- cc65_free_dbginfo (Info);
-
- return 0;
-}
-
-
-
# ------------------------------------------------------------------------------
# The executable to build
-EXE = dbgtest
+EXE = dbgsh
+
+# Library dir
+COMMON = ../common
#
CC = gcc
-CFLAGS = -g -Wall -W
+CFLAGS = -g -Wall -W -I$(COMMON)
EBIND = emxbind
LDFLAGS =
# Object files to link
OBJS = dbginfo.o \
- dbgtest.o
+ dbgsh.o
+LIBS = $(COMMON)/common.a
# ------------------------------------------------------------------------------
# Makefile targets
@$(MAKE) -f make/gcc.mak all
endif
-$(EXE): $(OBJS)
- $(CC) $(OBJS) $(LDFLAGS) -o $@
+$(EXE): $(OBJS) $(LIBS)
+ $(CC) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
@if [ $(OS2_SHELL) ] ; then $(EBIND) $(EXE) ; fi
clean: