.HTML "How to Use the Plan 9 C Compiler .TL How to Use the Plan 9 C Compiler .AU Rob Pike rob@plan9.bell-labs.com .SH Introduction .PP The C compiler on Plan 9 is a wholly new program; in fact it was the first piece of software written for what would eventually become Plan 9 from Bell Labs. Programmers familiar with existing C compilers will find a number of differences in both the language the Plan 9 compiler accepts and in how the compiler is used. .PP The compiler is really a set of compilers, one for each architecture \(em MIPS, SPARC, Motorola 68020, Intel 386, etc. \(em that accept a dialect of ANSI C and efficiently produce fairly good code for the target machine. There is a packaging of the compiler that accepts strict ANSI C for a POSIX environment, but this document focuses on the native Plan 9 environment, that in which all the system source and almost all the utilities are written. .SH Source .PP The language accepted by the compilers is the core ANSI C language with some modest extensions, a greatly simplified preprocessor, a smaller library that includes system calls and related facilities, and a completely different structure for include files. .PP Official ANSI C accepts the old (K&R) style of declarations for functions; the Plan 9 compilers are more demanding. Without an explicit run-time flag .CW -B ) ( whose use is discouraged, the compilers insist on new-style function declarations, that is, prototypes for function arguments. The function declarations in the libraries' include files are all in the new style so the interfaces are checked at compile time. For C programmers who have not yet switched to function prototypes the clumsy syntax may seem repellent but the payoff in stronger typing is substantial. Those who wish to import existing software to Plan 9 are urged to use the opportunity to update their code. .PP The compilers include an integrated preprocessor that accepts the familiar .CW #include , .CW #define for macros both with and without arguments, .CW #undef , .CW #line , .CW #ifdef , .CW #ifndef , and .CW #endif . It supports neither .CW #if nor .CW ## , although it does honor a few .CW #pragmas . The .CW #if directive was omitted because it greatly complicates the preprocessor, is never necessary, and is usually abused. Conditional compilation in general makes code hard to understand; the Plan 9 source uses it sparingly. Also, because the compilers remove dead code, regular .CW if statements with constant conditions are more readable equivalents to many .CW #ifs . To compile imported code ineluctably fouled by .CW #if there is a separate command, .CW /bin/cpp , that implements the complete ANSI C preprocessor specification. .PP Include files fall into two groups: machine-dependent and machine-independent. The machine-independent files occupy the directory .CW /sys/include ; the others are placed in a directory appropriate to the machine, such as .CW /mips/include . The compiler searches for include files first in the machine-dependent directory and then in the machine-independent directory. At the time of writing there are thirty-one machine-independent include files and two (per machine) machine-dependent ones: .CW and .CW . The first describes the layout of registers on the system stack, for use by the debugger. The second defines some architecture-dependent types such as .CW jmp_buf for .CW setjmp and the .CW va_arg and .CW va_list macros for handling arguments to variadic functions, as well as a set of .CW typedef abbreviations for .CW unsigned .CW short and so on. .PP Here is an excerpt from .CW /68020/include/u.h : .P1 #define nil ((void*)0) typedef unsigned short ushort; typedef unsigned char uchar; typedef unsigned long ulong; typedef unsigned int uint; typedef signed char schar; typedef long long vlong; typedef long jmp_buf[2]; #define JMPBUFSP 0 #define JMPBUFPC 1 #define JMPBUFDPC 0 .P2 Plan 9 programs use .CW nil for the name of the zero-valued pointer. The type .CW vlong is the largest integer type available; on most architectures it is a 64-bit value. A couple of other types in .CW are .CW u32int , which is guaranteed to have exactly 32 bits (a possibility on all the supported architectures) and .CW mpdigit , which is used by the multiprecision math package .CW . The .CW #define constants permit an architecture-independent (but compiler-dependent) implementation of stack-switching using .CW setjmp and .CW longjmp . .PP Every Plan 9 C program begins .P1 #include .P2 because all the other installed header files use the .CW typedefs declared in .CW . .PP In strict ANSI C, include files are grouped to collect related functions in a single file: one for string functions, one for memory functions, one for I/O, and none for system calls. Each include file is protected by an .CW #ifdef to guarantee its contents are seen by the compiler only once. Plan 9 takes a different approach. Other than a few include files that define external formats such as archives, the files in .CW /sys/include correspond to .I libraries. If a program is using a library, it includes the corresponding header. The default C library comprises string functions, memory functions, and so on, largely as in ANSI C, some formatted I/O routines, plus all the system calls and related functions. To use these functions, one must .CW #include the file .CW , which in turn must follow .CW , to define their prototypes for the compiler. Here is the complete source to the traditional first C program: .P1 #include #include void main(void) { print("hello world\en"); exits(0); } .P2 The .CW print routine and its relatives .CW fprint and .CW sprint resemble the similarly-named functions in Standard I/O but are not attached to a specific I/O library. In Plan 9 .CW main is not integer-valued; it should call .CW exits , which takes a string argument (or null; here ANSI C promotes the 0 to a .CW char* ). All these functions are, of course, documented in the Programmer's Manual. .PP To use .CW printf , .CW must be included to define the function prototype for .CW printf : .P1 #include #include #include void main(int argc, char *argv[]) { printf("%s: hello world; argc = %d\en", argv[0], argc); exits(0); } .P2 In practice, Standard I/O is not used much in Plan 9. I/O libraries are discussed in a later section of this document. .PP There are libraries for handling regular expressions, raster graphics, windows, and so on, and each has an associated include file. The manual for each library states which include files are needed. The files are not protected against multiple inclusion and themselves contain no nested .CW #includes . Instead the programmer is expected to sort out the requirements and to .CW #include the necessary files once at the top of each source file. In practice this is trivial: this way of handling include files is so straightforward that it is rare for a source file to contain more than half a dozen .CW #includes . .PP The compilers do their own register allocation so the .CW register keyword is ignored. For different reasons, .CW volatile and .CW const are also ignored. .PP To make it easier to share code with other systems, Plan 9 has a version of the compiler, .CW pcc , that provides the standard ANSI C preprocessor, headers, and libraries with POSIX extensions. .CW Pcc is recommended only when broad external portability is mandated. It compiles slower, produces slower code (it takes extra work to simulate POSIX on Plan 9), eliminates those parts of the Plan 9 interface not related to POSIX, and illustrates the clumsiness of an environment designed by committee. .CW Pcc is described in more detail in .I APE\(emThe ANSI/POSIX Environment, .R by Howard Trickey. .SH Process .PP Each CPU architecture supported by Plan 9 is identified by a single, arbitrary, alphanumeric character: .CW k for SPARC, .CW q for Motorola Power PC 630 and 640, .CW v for MIPS, .CW 0 for little-endian MIPS, .CW 1 for Motorola 68000, .CW 2 for Motorola 68020 and 68040, .CW 5 for Acorn ARM 7500, .CW 6 for AMD 64, .CW 7 for DEC Alpha, .CW 8 for Intel 386, and .CW 9 for AMD 29000. The character labels the support tools and files for that architecture. For instance, for the 68020 the compiler is .CW 2c , the assembler is .CW 2a , the link editor/loader is .CW 2l , the object files are suffixed .CW \&.2 , and the default name for an executable file is .CW 2.out . Before we can use the compiler we therefore need to know which machine we are compiling for. The next section explains how this decision is made; for the moment assume we are building 68020 binaries and make the mental substitution for .CW 2 appropriate to the machine you are actually using. .PP To convert source to an executable binary is a two-step process. First run the compiler, .CW 2c , on the source, say .CW file.c , to generate an object file .CW file.2 . Then run the loader, .CW 2l , to generate an executable .CW 2.out that may be run (on a 680X0 machine): .P1 2c file.c 2l file.2 2.out .P2 The loader automatically links with whatever libraries the program needs, usually including the standard C library as defined by .CW . Of course the compiler and loader have lots of options, both familiar and new; see the manual for details. The compiler does not generate an executable automatically; the output of the compiler must be given to the loader. Since most compilation is done under the control of .CW mk (see below), this is rarely an inconvenience. .PP The distribution of work between the compiler and loader is unusual. The compiler integrates preprocessing, parsing, register allocation, code generation and some assembly. Combining these tasks in a single program is part of the reason for the compiler's efficiency. The loader does instruction selection, branch folding, instruction scheduling, and writes the final executable. There is no separate C preprocessor and no assembler in the usual pipeline. Instead the intermediate object file (here a .CW \&.2 file) is a type of binary assembly language. The instructions in the intermediate format are not exactly those in the machine. For example, on the 68020 the object file may specify a MOVE instruction but the loader will decide just which variant of the MOVE instruction \(em MOVE immediate, MOVE quick, MOVE address, etc. \(em is most efficient. .PP The assembler, .CW 2a , is just a translator between the textual and binary representations of the object file format. It is not an assembler in the traditional sense. It has limited macro capabilities (the same as the integral C preprocessor in the compiler), clumsy syntax, and minimal error checking. For instance, the assembler will accept an instruction (such as memory-to-memory MOVE on the MIPS) that the machine does not actually support; only when the output of the assembler is passed to the loader will the error be discovered. The assembler is intended only for writing things that need access to instructions invisible from C, such as the machine-dependent part of an operating system; very little code in Plan 9 is in assembly language. .PP The compilers take an option .CW -S that causes them to print on their standard output the generated code in a format acceptable as input to the assemblers. This is of course merely a formatting of the data in the object file; therefore the assembler is just an ASCII-to-binary converter for this format. Other than the specific instructions, the input to the assemblers is largely architecture-independent; see ``A Manual for the Plan 9 Assembler'', by Rob Pike, for more information. .PP The loader is an integral part of the compilation process. Each library header file contains a .CW #pragma that tells the loader the name of the associated archive; it is not necessary to tell the loader which libraries a program uses. The C run-time startup is found, by default, in the C library. The loader starts with an undefined symbol, .CW _main , that is resolved by pulling in the run-time startup code from the library. (The loader undefines .CW _mainp when profiling is enabled, to force loading of the profiling start-up instead.) .PP Unlike its counterpart on other systems, the Plan 9 loader rearranges data to optimize access. This means the order of variables in the loaded program is unrelated to its order in the source. Most programs don't care, but some assume that, for example, the variables declared by .P1 int a; int b; .P2 will appear at adjacent addresses in memory. On Plan 9, they won't. .SH Heterogeneity .PP When the system starts or a user logs in the environment is configured so the appropriate binaries are available in .CW /bin . The configuration process is controlled by an environment variable, .CW $cputype , with value such as .CW mips , .CW 68020 , .CW 386 , or .CW sparc . For each architecture there is a directory in the root, with the appropriate name, that holds the binary and library files for that architecture. Thus .CW /mips/lib contains the object code libraries for MIPS programs, .CW /mips/include holds MIPS-specific include files, and .CW /mips/bin has the MIPS binaries. These binaries are attached to .CW /bin at boot time by binding .CW /$cputype/bin to .CW /bin , so .CW /bin always contains the correct files. .PP The MIPS compiler, .CW vc , by definition produces object files for the MIPS architecture, regardless of the architecture of the machine on which the compiler is running. There is a version of .CW vc compiled for each architecture: .CW /mips/bin/vc , .CW /68020/bin/vc , .CW /sparc/bin/vc , and so on, each capable of producing MIPS object files regardless of the native instruction set. If one is running on a SPARC, .CW /sparc/bin/vc will compile programs for the MIPS; if one is running on machine .CW $cputype , .CW /$cputype/bin/vc will compile programs for the MIPS. .PP Because of the bindings that assemble .CW /bin , the shell always looks for a command, say .CW date , in .CW /bin and automatically finds the file .CW /$cputype/bin/date . Therefore the MIPS compiler is known as just .CW vc ; the shell will invoke .CW /bin/vc and that is guaranteed to be the version of the MIPS compiler appropriate for the machine running the command. Regardless of the architecture of the compiling machine, .CW /bin/vc is .I always the MIPS compiler. .PP Also, the output of .CW vc and .CW vl is completely independent of the machine type on which they are executed: .CW \&.v files compiled (with .CW vc ) on a SPARC may be linked (with .CW vl ) on a 386. (The resulting .CW v.out will run, of course, only on a MIPS.) Similarly, the MIPS libraries in .CW /mips/lib are suitable for loading with .CW vl on any machine; there is only one set of MIPS libraries, not one set for each architecture that supports the MIPS compiler. .SH Heterogeneity and \f(CWmk\fP .PP Most software on Plan 9 is compiled under the control of .CW mk , a descendant of .CW make that is documented in the Programmer's Manual. A convention used throughout the .CW mkfiles makes it easy to compile the source into binary suitable for any architecture. .PP The variable .CW $cputype is advisory: it reports the architecture of the current environment, and should not be modified. A second variable, .CW $objtype , is used to set which architecture is being .I compiled for. The value of .CW $objtype can be used by a .CW mkfile to configure the compilation environment. .PP In each machine's root directory there is a short .CW mkfile that defines a set of macros for the compiler, loader, etc. Here is .CW /mips/mkfile : .P1 , contains no buffered I/O package. It does have several entry points for printing formatted text: .CW print outputs text to the standard output, .CW fprint outputs text to a specified integer file descriptor, and .CW sprint places text in a character array. To access library routines for buffered I/O, a program must explicitly include the header file associated with an appropriate library. .PP The recommended I/O library, used by most Plan 9 utilities, is .CW bio (buffered I/O), defined by .CW . There also exists an implementation of ANSI Standard I/O, .CW stdio . .PP .CW Bio is small and efficient, particularly for buffer-at-a-time or line-at-a-time I/O. Even for character-at-a-time I/O, however, it is significantly faster than the Standard I/O library, .CW stdio . Its interface is compact and regular, although it lacks a few conveniences. The most noticeable is that one must explicitly define buffers for standard input and output; .CW bio does not predefine them. Here is a program to copy input to output a byte at a time using .CW bio : .P1 #include #include #include Biobuf bin; Biobuf bout; main(void) { int c; Binit(&bin, 0, OREAD); Binit(&bout, 1, OWRITE); while((c=Bgetc(&bin)) != Beof) Bputc(&bout, c); exits(0); } .P2 For peak performance, we could replace .CW Bgetc and .CW Bputc by their equivalent in-line macros .CW BGETC and .CW BPUTC but the performance gain would be modest. For more information on .CW bio , see the Programmer's Manual. .PP Perhaps the most dramatic difference in the I/O interface of Plan 9 from other systems' is that text is not ASCII. The format for text in Plan 9 is a byte-stream encoding of 16-bit characters. The character set is based on the Unicode Standard and is backward compatible with ASCII: characters with value 0 through 127 are the same in both sets. The 16-bit characters, called .I runes in Plan 9, are encoded using a representation called UTF, an encoding that is becoming accepted as a standard. (ISO calls it UTF-8; throughout Plan 9 it's just called UTF.) UTF defines multibyte sequences to represent character values from 0 to 65535. In UTF, character values up to 127 decimal, 7F hexadecimal, represent themselves, so straight ASCII files are also valid UTF. Also, UTF guarantees that bytes with values 0 to 127 (NUL to DEL, inclusive) will appear only when they represent themselves, so programs that read bytes looking for plain ASCII characters will continue to work. Any program that expects a one-to-one correspondence between bytes and characters will, however, need to be modified. An example is parsing file names. File names, like all text, are in UTF, so it is incorrect to search for a character in a string by .CW strchr(filename, .CW c) because the character might have a multi-byte encoding. The correct method is to call .CW utfrune(filename, .CW c) , defined in .I rune (2), which interprets the file name as a sequence of encoded characters rather than bytes. In fact, even when you know the character is a single byte that can represent only itself, it is safer to use .CW utfrune because that assumes nothing about the character set and its representation. .PP The library defines several symbols relevant to the representation of characters. Any byte with unsigned value less than .CW Runesync will not appear in any multi-byte encoding of a character. .CW Utfrune compares the character being searched against .CW Runesync to see if it is sufficient to call .CW strchr or if the byte stream must be interpreted. Any byte with unsigned value less than .CW Runeself is represented by a single byte with the same value. Finally, when errors are encountered converting to runes from a byte stream, the library returns the rune value .CW Runeerror and advances a single byte. This permits programs to find runes embedded in binary data. .PP .CW Bio includes routines .CW Bgetrune and .CW Bputrune to transform the external byte stream UTF format to and from internal 16-bit runes. Also, the .CW %s format to .CW print accepts UTF; .CW %c prints a character after narrowing it to 8 bits. The .CW %S format prints a null-terminated sequence of runes; .CW %C prints a character after narrowing it to 16 bits. For more information, see the Programmer's Manual, in particular .I utf (6) and .I rune (2), and the paper, ``Hello world, or Καλημέρα κόσμε, or\ \f(Jpこんにちは 世界\f1'', by Rob Pike and Ken Thompson; there is not room for the full story here. .PP These issues affect the compiler in several ways. First, the C source is in UTF. ANSI says C variables are formed from ASCII alphanumerics, but comments and literal strings may contain any characters encoded in the native encoding, here UTF. The declaration .P1 char *cp = "abcÿ"; .P2 initializes the variable .CW cp to point to an array of bytes holding the UTF representation of the characters .CW abcÿ. The type .CW Rune is defined in .CW to be .CW ushort , which is also the `wide character' type in the compiler. Therefore the declaration .P1 Rune *rp = L"abcÿ"; .P2 initializes the variable .CW rp to point to an array of unsigned short integers holding the 16-bit values of the characters .CW abcÿ . Note that in both these declarations the characters in the source that represent .CW "abcÿ" are the same; what changes is how those characters are represented in memory in the program. The following two lines: .P1 print("%s\en", "abcÿ"); print("%S\en", L"abcÿ"); .P2 produce the same UTF string on their output, the first by copying the bytes, the second by converting from runes to bytes. .PP In C, character constants are integers but narrowed through the .CW char type. The Unicode character .CW ÿ has value 255, so if the .CW char type is signed, the constant .CW 'ÿ' has value \-1 (which is equal to EOF). On the other hand, .CW L'ÿ' narrows through the wide character type, .CW ushort , and therefore has value 255. .PP Finally, although it's not ANSI C, the Plan 9 C compilers assume any character with value above .CW Runeself is an alphanumeric, so α is a legal, if non-portable, variable name. .SH Arguments .PP Some macros are defined in .CW for parsing the arguments to .CW main() . They are described in .I ARG (2) but are fairly self-explanatory. There are four macros: .CW ARGBEGIN and .CW ARGEND are used to bracket a hidden .CW switch statement within which .CW ARGC returns the current option character (rune) being processed and .CW ARGF returns the argument to the option, as in the loader option .CW -o .CW file . Here, for example, is the code at the beginning of .CW main() in .CW ramfs.c (see .I ramfs (1)) that cracks its arguments: .P1 void main(int argc, char *argv[]) { char *defmnt; int p[2]; int mfd[2]; int stdio = 0; defmnt = "/tmp"; ARGBEGIN{ case 'i': defmnt = 0; stdio = 1; mfd[0] = 0; mfd[1] = 1; break; case 's': defmnt = 0; break; case 'm': defmnt = ARGF(); break; default: usage(); }ARGEND .P2 .SH Extensions .PP The compiler has several extensions to ANSI C, all of which are used extensively in the system source. First, .I structure .I displays permit .CW struct expressions to be formed dynamically. Given these declarations: .P1 typedef struct Point Point; typedef struct Rectangle Rectangle; struct Point { int x, y; }; struct Rectangle { Point min, max; }; Point p, q, add(Point, Point); Rectangle r; int x, y; .P2 this assignment may appear anywhere an assignment is legal: .P1 r = (Rectangle){add(p, q), (Point){x, y+3}}; .P2 The syntax is the same as for initializing a structure but with a leading cast. .PP If an .I anonymous .I structure or .I union is declared within another structure or union, the members of the internal structure or union are addressable without prefix in the outer structure. This feature eliminates the clumsy naming of nested structures and, particularly, unions. For example, after these declarations, .P1 struct Lock { int locked; }; struct Node { int type; union{ double dval; double fval; long lval; }; /* anonymous union */ struct Lock; /* anonymous structure */ } *node; void lock(struct Lock*); .P2 one may refer to .CW node->type , .CW node->dval , .CW node->fval , .CW node->lval , and .CW node->locked . Moreover, the address of a .CW struct .CW Node may be used without a cast anywhere that the address of a .CW struct .CW Lock is used, such as in argument lists. The compiler automatically promotes the type and adjusts the address. Thus one may invoke .CW lock(node) . .PP Anonymous structures and unions may be accessed by type name if (and only if) they are declared using a .CW typedef name. For example, using the above declaration for .CW Point , one may declare .P1 struct { int type; Point; } p; .P2 and refer to .CW p.Point . .PP In the initialization of arrays, a number in square brackets before an element sets the index for the initialization. For example, to initialize some elements in a table of function pointers indexed by ASCII character, .P1 void percent(void), slash(void); void (*func[128])(void) = { ['%'] percent, ['/'] slash, }; .P2 .LP A similar syntax allows one to initialize structure elements: .P1 Point p = { .y 100, .x 200 }; .P2 These initialization syntaxes were later added to ANSI C, with the addition of an equals sign between the index or tag and the value. The Plan 9 compiler accepts either form. .PP Finally, the declaration .P1 extern register reg; .P2 .I this "" ( appearance of the register keyword is not ignored) allocates a global register to hold the variable .CW reg . External registers must be used carefully: they need to be declared in .I all source files and libraries in the program to guarantee the register is not allocated temporarily for other purposes. Especially on machines with few registers, such as the i386, it is easy to link accidentally with code that has already usurped the global registers and there is no diagnostic when this happens. Used wisely, though, external registers are powerful. The Plan 9 operating system uses them to access per-process and per-machine data structures on a multiprocessor. The storage class they provide is hard to create in other ways. .SH The compile-time environment .PP The code generated by the compilers is `optimized' by default: variables are placed in registers and peephole optimizations are performed. The compiler flag .CW -N disables these optimizations. Registerization is done locally rather than throughout a function: whether a variable occupies a register or the memory location identified in the symbol table depends on the activity of the variable and may change throughout the life of the variable. The .CW -N flag is rarely needed; its main use is to simplify debugging. There is no information in the symbol table to identify the registerization of a variable, so .CW -N guarantees the variable is always where the symbol table says it is. .PP Another flag, .CW -w , turns .I on warnings about portability and problems detected in flow analysis. Most code in Plan 9 is compiled with warnings enabled; these warnings plus the type checking offered by function prototypes provide most of the support of the Unix tool .CW lint more accurately and with less chatter. Two of the warnings, `used and not set' and `set and not used', are almost always accurate but may be triggered spuriously by code with invisible control flow, such as in routines that call .CW longjmp . The compiler statements .P1 SET(v1); USED(v2); .P2 decorate the flow graph to silence the compiler. Either statement accepts a comma-separated list of variables. Use them carefully: they may silence real errors. For the common case of unused parameters to a function, leaving the name off the declaration silences the warnings. That is, listing the type of a parameter but giving it no associated variable name does the trick. .SH Debugging .PP There are two debuggers available on Plan 9. The first, and older, is .CW db , a revision of Unix .CW adb . The other, .CW acid , is a source-level debugger whose commands are statements in a true programming language. .CW Acid is the preferred debugger, but since it borrows some elements of .CW db , notably the formats for displaying values, it is worth knowing a little bit about .CW db . .PP Both debuggers support multiple architectures in a single program; that is, the programs are .CW db and .CW acid , not for example .CW vdb and .CW vacid . They also support cross-architecture debugging comfortably: one may debug a 68020 binary on a MIPS. .PP Imagine a program has crashed mysteriously: .P1 % X11/X Fatal server bug! failed to create default stipple X 106: suicide: sys: trap: fault read addr=0x0 pc=0x00105fb8 % .P2 When a process dies on Plan 9 it hangs in the `broken' state for debugging. Attach a debugger to the process by naming its process id: .P1 % acid 106 /proc/106/text:mips plan 9 executable /sys/lib/acid/port /sys/lib/acid/mips acid: .P2 The .CW acid function .CW stk() reports the stack traceback: .P1 acid: stk() At pc:0x105fb8:abort+0x24 /sys/src/ape/lib/ap/stdio/abort.c:6 abort() /sys/src/ape/lib/ap/stdio/abort.c:4 called from FatalError+#4e /sys/src/X/mit/server/dix/misc.c:421 FatalError(s9=#e02, s8=#4901d200, s7=#2, s6=#72701, s5=#1, s4=#7270d, s3=#6, s2=#12, s1=#ff37f1c, s0=#6, f=#7270f) /sys/src/X/mit/server/dix/misc.c:416 called from gnotscreeninit+#4ce /sys/src/X/mit/server/ddx/gnot/gnot.c:792 gnotscreeninit(snum=#0, sc=#80db0) /sys/src/X/mit/server/ddx/gnot/gnot.c:766 called from AddScreen+#16e /n/bootes/sys/src/X/mit/server/dix/main.c:610 AddScreen(pfnInit=0x0000129c,argc=0x00000001,argv=0x7fffffe4) /sys/src/X/mit/server/dix/main.c:530 called from InitOutput+0x80 /sys/src/X/mit/server/ddx/brazil/brddx.c:522 InitOutput(argc=0x00000001,argv=0x7fffffe4) /sys/src/X/mit/server/ddx/brazil/brddx.c:511 called from main+0x294 /sys/src/X/mit/server/dix/main.c:225 main(argc=0x00000001,argv=0x7fffffe4) /sys/src/X/mit/server/dix/main.c:136 called from _main+0x24 /sys/src/ape/lib/ap/mips/main9.s:8 .P2 The function .CW lstk() is similar but also reports the values of local variables. Note that the traceback includes full file names; this is a boon to debugging, although it makes the output much noisier. .PP To use .CW acid well you will need to learn its input language; see the ``Acid Manual'', by Phil Winterbottom, for details. For simple debugging, however, the information in the manual page is sufficient. In particular, it describes the most useful functions for examining a process. .PP The compiler does not place information describing the types of variables in the executable, but a compile-time flag provides crude support for symbolic debugging. The .CW -a flag to the compiler suppresses code generation and instead emits source text in the .CW acid language to format and display data structure types defined in the program. The easiest way to use this feature is to put a rule in the .CW mkfile : .P1 syms: main.$O $CC -a main.c > syms .P2 Then from within .CW acid , .P1 acid: include("sourcedirectory/syms") .P2 to read in the relevant definitions. (For multi-file source, you need to be a little fancier; see .I 2c (1)). This text includes, for each defined compound type, a function with that name that may be called with the address of a structure of that type to display its contents. For example, if .CW rect is a global variable of type .CW Rectangle , one may execute .P1 Rectangle(*rect) .P2 to display it. The .CW * (indirection) operator is necessary because of the way .CW acid works: each global symbol in the program is defined as a variable by .CW acid , with value equal to the .I address of the symbol. .PP Another common technique is to write by hand special .CW acid code to define functions to aid debugging, initialize the debugger, and so on. Conventionally, this is placed in a file called .CW acid in the source directory; it has a line .P1 include("sourcedirectory/syms"); .P2 to load the compiler-produced symbols. One may edit the compiler output directly but it is wiser to keep the hand-generated .CW acid separate from the machine-generated. .PP To make things simple, the default rules in the system .CW mkfiles include entries to make .CW foo.acid from .CW foo.c , so one may use .CW mk to automate the production of .CW acid definitions for a given C source file. .PP There is much more to say here. See .CW acid manual page, the reference manual, or the paper ``Acid: A Debugger Built From A Language'', also by Phil Winterbottom.