/[gxemul]/trunk/doc/technical.html
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/doc/technical.html

Parent Directory Parent Directory | Revision Log Revision Log


Revision 10 - (show annotations)
Mon Oct 8 16:18:27 2007 UTC (16 years, 6 months ago) by dpavlin
File MIME type: text/html
File size: 19751 byte(s)
++ trunk/HISTORY	(local)
$Id: HISTORY,v 1.815 2005/06/27 23:04:35 debug Exp $
20050617	Experimenting some more with netbooting OpenBSD/sgi. Adding
		a hack which allows emulated ethernet networks to be
		distributed across multiple emulator processes.
20050618	Minor updates (documentation, dummy YAMON emulation, etc).
20050620	strcpy/strcat -> strlcpy/strlcat updates.
		Some more progress on evbmips (Malta).
20050621	Adding a section to doc/configfiles.html about ethernet
		emulation across multiple hosts.
		Beginning the work on the ARM translation engine (using the
		dynamic-but-not-binary translation method).
		Fixing a bintrans bug: 0x9fc00000 should always be treated as
		PROM area, just as 0xbfc00000 is.
		Minor progress on Malta emulation (the PCI-ISA bus).
20050622	NetBSD/evbmips can now be installed (using another emulated
		machine) and run (including userland and so on). :-)
		Spliting up the bintrans haddr_entry field into two (one for
		read, one for write). Probably not much of a speed increase,
		though.
		Updating some NetBSD 2.0 -> 2.0.2 in the documentation.
20050623	Minor updates (documentation, the TODO file, etc).
		gzipped kernels are now always automagically gunzipped when
		loaded.
20050624	Adding a dummy Playstation Portable (PSP) mode, just barely
		enough to run Hello World (in weird colors :-).
		Removing the -b command line option; old bintrans is enabled
		by default instead. It makes more sense.
		Trying to finally fix the non-working performance measurement
		thing (instr/second etc).
20050625	Continuing on the essential basics for ARM emulation. Two
		instructions seem to work, a branch and a simple "mov". (The
		mov arguments are not correct yet.) Performance is definitely
		reasonable.
		Various other minor updates.
		Adding the ARM "bl" instruction.
		Adding support for combining multiple ARM instructions into one
		function call. ("mov" + "mov" is the only one implemented so
		far, but it seems to work.)
		Cleaning up some IP32 interrupt things (crime/mace); disabling
		the PS/2 keyboard controller on IP32, so that NetBSD/sgimips
		boots into userland again.
20050626	Finally! NetBSD/sgimips netboots. Adding instructions to
		doc/guestoses.html on how to set up an nfs server etc.
		Various other minor fixes.
		Playstation Portable ".pbp" files can now be used directly.
		(The ELF part of the .pbp is extracted transparently.)
		Converting some sprintf -> snprintf.
		Adding some more instructions to the ARM disassembler.
20050627	More ARM updates. Adding some simple ldr(b), str(b),
		cmps, and conditional branch instructions, enough to run
		a simple Hello World program.
		All ARM instructions are now inlined/generated for all possible
		condition codes.
		Adding add and sub, and more load/store instructions.
		Removing dummy files: cpu_alpha.c, cpu_hppa.c, and cpu_sparc.c.
		Some minor documentation updates; preparing for a 0.3.4
		release. Updating some URLs.

==============  RELEASE 0.3.4  ==============


1 <html><head><title>GXemul documentation: Technical details</title>
2 <meta name="robots" content="noarchive,nofollow,noindex"></head>
3 <body bgcolor="#f8f8f8" text="#000000" link="#4040f0" vlink="#404040" alink="#ff0000">
4 <table border=0 width=100% bgcolor="#d0d0d0"><tr>
5 <td width=100% align=center valign=center><table border=0 width=100%><tr>
6 <td align="left" valign=center bgcolor="#d0efff"><font color="#6060e0" size="6">
7 <b>GXemul documentation:</b></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
8 <font color="#000000" size="6"><b>Technical details</b>
9 </font></td></tr></table></td></tr></table><p>
10
11 <!--
12
13 $Id: technical.html,v 1.53 2005/06/27 17:31:50 debug Exp $
14
15 Copyright (C) 2004-2005 Anders Gavare. All rights reserved.
16
17 Redistribution and use in source and binary forms, with or without
18 modification, are permitted provided that the following conditions are met:
19
20 1. Redistributions of source code must retain the above copyright
21 notice, this list of conditions and the following disclaimer.
22 2. Redistributions in binary form must reproduce the above copyright
23 notice, this list of conditions and the following disclaimer in the
24 documentation and/or other materials provided with the distribution.
25 3. The name of the author may not be used to endorse or promote products
26 derived from this software without specific prior written permission.
27
28 THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
29 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
32 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 SUCH DAMAGE.
39
40 -->
41
42
43 <a href="./">Back to the index</a>
44
45 <p><br>
46 <h2>Technical details</h2>
47
48 <p>This page describes some of the internals of GXemul.
49
50 <p>
51 <ul>
52 <li><a href="#speed">Speed and emulation modes</a>
53 <li><a href="#net">Networking</a>
54 <li><a href="#devices">Emulation of hardware devices</a>
55 <li><a href="#regtest">Regression tests</a>
56 </ul>
57
58
59
60
61
62
63 <p><br>
64 <a name="speed"></a>
65 <h3>Speed and emulation modes</h3>
66
67 So, how fast is GXemul? There is no good answer to this. There is
68 especially no answer to the question <b>What is the slowdown factor?</b>,
69 because the host architecture and emulated architecture can usually not be
70 compared just like that.
71
72 <p>Performance depends on several factors, including (but not limited to)
73 host architecture, host clock speed, which compiler and compiler flags
74 were used to build the emulator, what the workload is, and so on. For
75 example, if an emulated operating system tries to read a block from disk,
76 from its point of view the read was instantaneous (no waiting). So 1 MIPS
77 in an emulated OS might have taken more than one million instructions on a
78 real machine.
79
80 <p>Also, if the emulator says it has executed 1 million instructions, and
81 the CPU family in question was capable of scalar execution (i.e. one cycle
82 per instruction), it might still have taken more than 1 million cycles on
83 a real machine because of cache misses and similar micro-architectural
84 penalties that are not simulated by GXemul.
85
86 <p>Because of these issues, it is in my opinion best to measure
87 performance as the actual (real-world) time it takes to perform a task
88 with the emulator. Typical examples would be "How long does it take to
89 install NetBSD?", or "How long does it take to compile XYZ inside NetBSD
90 in the emulator?".
91
92 <p>The emulation technique used varies depending on which processor type
93 is being emulated. (One of my main goals with GXemul is to experiment with
94 different kinds of emulation, so these might change in the future.)
95
96 <ul>
97 <li><b>MIPS</b><br>
98 There are two emulation modes. The most important one is an
99 implementation of a <i>dynamic binary translator</i>.
100 (Compared to real binary translators, though, GXemul's bintrans
101 subsystem is very simple and does not perform very well.)
102 This mode can be used on Alpha and i386 host. The other emulation
103 mode is simple interpretation, where an instruction is read from
104 emulated memory, and interpreted one-at-a-time. (Slow, but it
105 works. It can be forcefully used by using the <tt>-B</tt> command
106 line option.)
107 <p>
108 <li><b>ARM</b><br>
109 This mode does not really work yet, but will use
110 dynamic translation, but not binary translation. Stay tuned. :-)
111 <p>
112 <li><b>URISC</b><br>
113 Simple interpretation, one instruction at a time. There is probably
114 no other way to emulate URISC, because it relies too heavily
115 on self-modifying code.
116 <p>
117 <li><b>POWER/PowerPC</b><br>
118 This emulation mode is very much unfinished, but still enabled by
119 default. So far it uses plain interpretation, where an instruction
120 is read from emulated memory, and interpreted one at a time.
121 Slow. Not very interesting.
122 <p>
123 <li><b>x86</b><br>
124 Although too unstable and non-working to be enabled by default,
125 there is some code for emulating x86 machines. It simply reads
126 one instruction at a time from emulated memory, and executes it.
127 This is as slow as it gets. Not very interesting.
128 </ul>
129
130
131
132
133
134
135
136 <p><br>
137 <a name="net"></a>
138 <h3>Networking</h3>
139
140 <font color="#ff0000">NOTE/TODO: This section is very old and a bit
141 out of date.</font>
142
143 <p>Running an entire operating system under emulation is very interesting
144 in itself, but for several reasons, running a modern OS without access to
145 TCP/IP networking is a bit akward. Hence, I feel the need to implement
146 TCP/IP (networking) support in the emulator.
147
148 <p>
149 As far as I have understood it, there seems to be two different ways to go:
150
151 <ol>
152 <li>Forward ethernet packets from the emulated ethernet controller to
153 the host machine's ethernet controller, and capture incoming
154 packets on the host's controller, giving them back to the
155 emulated OS. Characteristics are:
156 <ul>
157 <li>Requires <i>direct</i> access to the host's NIC, which
158 means on most platforms that the emulator cannot be
159 run as a normal user!
160 <li>Reduced portability, as not every host operating system
161 uses the same programming interface for dealing with
162 hardware ethernet controllers directly.
163 <li>When run on a switched network, it might be problematic to
164 connect from the emulated OS to the OS running on the
165 host, as packets sent out on the host's NIC are not
166 received by itself. (?)
167 <li>All specific networking protocols will be handled by the
168 physical network.
169 </ul>
170 <p>
171 or
172 <p>
173 <li>Whenever the emulated ethernet controller wishes to send a packet,
174 the emulator looks at the packet and creates a response. Packets
175 that can have an immediate response never go outside the emulator,
176 other packet types have to be converted into suitable other
177 connection types (UDP, TCP, etc). Characteristics:
178 <ul>
179 <li>Each packet type sent out on the emulated NIC must be handled.
180 This means that I have to do a lot of coding.
181 (I like this, because it gives me an opportunity to
182 learn about networking protocols.)
183 <li>By not relying on access to the host's NIC directly,
184 portability is maintained. (It would be sad if the networking
185 portion of a portable emulator isn't as portable as the
186 rest of the emulator.)
187 <li>The emulator can be run as a normal user process, does
188 not require root privilegies.
189 <li>Connecting from the emulated OS to the host's OS should
190 not be problematic.
191 <li>The emulated OS will experience the network just as a single
192 machine behind a NAT gateway/firewall would. The emulated
193 OS is thus automatically protected from the outside world.
194 </ul>
195 </ol>
196
197 <p>
198 Some emulators/simulators use the first approach, while others use the
199 second. I think that SIMH and QEMU are examples of emulators using the
200 first and second approach, respectively.
201
202 <p>
203 Since I have choosen the second kind of implementation, I have to write
204 support explicitly for any kind of network protocol that should be
205 supported. As of 2004-07-09, the following has been implemented and seems
206 to work under at least NetBSD/pmax and OpenBSD/pmax under DECstation 5000/200
207 emulation (-E dec -e 3max):
208
209 <p>
210 <ul>
211 <li>ARP requests sent out from the emulated NIC are interpreted,
212 and converted to ARP responses. (This is used by the emulated OS
213 to find out the MAC address of the gateway.)
214 <li>ICMP echo requests (that is the kind of packet produced by the
215 <b><tt>ping</tt></b> program) are interpreted and converted to ICMP echo
216 replies, <i>regardless of the IP address</i>. This means that
217 running ping from within the emulated OS will <i>always</i>
218 receive a response. The ping packets never leave the emulated
219 environment.
220 <li>UDP packets are interpreted and passed along to the outside world.
221 If the emulator receives an UDP packet from the outside world, it
222 is converted into an UDP packet for the emulated OS. (This is not
223 implemented very well yet, but seems to be enough for nameserver
224 lookups, tftp file transfers, and NFS mounts using UDP.)
225 <li>TCP packets are interpreted one at a time, similar to how UDP
226 packets are handled (but more state is kept for each connection).
227 <font color="#ff0000">NOTE: Much of the TCP handling code is very
228 ugly and hardcoded.</font>
229 <!--
230 <li>RARP is not implemented yet. (I haven't needed it so far.)
231 -->
232 </ul>
233
234 <p>
235 The gateway machine, which is the only "other" machine that the emulated
236 OS sees on its emulated network, works as a NAT-style firewall/gateway. It
237 usually has a fixed IPv4 address of <tt>10.0.0.254</tt>. An OS running in
238 the emulator would usually have an address of the form <tt>10.x.x.x</tt>;
239 a typical choice would be <tt>10.0.0.1</tt>.
240
241 <p>
242 Inside emulated NetBSD/pmax or OpenBSD/pmax, running the following
243 commands should configure the emulated NIC:
244 <pre>
245 # <b>ifconfig le0 10.0.0.1</b>
246 # <b>route add default 10.0.0.254</b>
247 add net default: gateway 10.0.0.254
248 </pre>
249
250 <p>
251 If you want nameserver lookups to work, you need a valid /etc/resolv.conf
252 as well:
253 <pre>
254 # <b>echo nameserver 129.16.1.3 > /etc/resolv.conf</b>
255 </pre>
256 (But replace <tt>129.16.1.3</tt> with the actual real-world IP address of
257 your nearest nameserver.)
258
259 <p>
260 Now, host lookups should work:
261 <pre>
262 # <b>host -a www.netbsd.org</b>
263 Trying null domain
264 rcode = 0 (Success), ancount=2
265 The following answer is not authoritative:
266 The following answer is not verified as authentic by the server:
267 www.netbsd.org 86400 IN AAAA 2001:4f8:4:7:290:27ff:feab:19a7
268 www.netbsd.org 86400 IN A 204.152.184.116
269 For authoritative answers, see:
270 netbsd.org 83627 IN NS uucp-gw-2.pa.dec.com
271 netbsd.org 83627 IN NS ns.netbsd.org
272 netbsd.org 83627 IN NS adns1.berkeley.edu
273 netbsd.org 83627 IN NS adns2.berkeley.edu
274 netbsd.org 83627 IN NS uucp-gw-1.pa.dec.com
275 Additional information:
276 ns.netbsd.org 83627 IN A 204.152.184.164
277 uucp-gw-1.pa.dec.com 172799 IN A 204.123.2.18
278 uucp-gw-2.pa.dec.com 172799 IN A 204.123.2.19
279 </pre>
280
281 <p>
282 At this point, UDP and TCP should (mostly) work.
283
284 <p>
285 Here is an example of how to configure a server machine and an emulated
286 client machine for sharing files via NFS:
287
288 <p>
289 (This is very useful if you want to share entire directory trees
290 between the emulated environment and another machine. These instruction
291 will work for FreeBSD, if you are running something else, use your
292 imagination to modify them.)
293
294 <p>
295 <ul>
296 <li>On the server, add a line to your /etc/exports file, exporting
297 the files you wish to use in the emulator:<pre>
298 <b>/tftpboot -mapall=nobody -ro 123.11.22.33</b>
299 </pre>
300 where 123.11.22.33 is the IP address of the machine running the
301 emulator process, as seen from the outside world.
302 <p>
303 <li>Then start up the programs needed to serve NFS via UDP. Note the
304 -n argument to mountd. This is needed to tell mountd to accept
305 connections from unprivileged ports (because the emulator does
306 not need to run as root).<pre>
307 # <b>portmap</b>
308 # <b>nfsd -u</b> &lt;--- u for UDP
309 # <b>mountd -n</b>
310 </pre>
311 <li>In the guest OS in the emulator, once you have ethernet and IPv4
312 configured so that you can use UDP, mounting the filesystem
313 should now be possible: (this example is for NetBSD/pmax
314 or OpenBSD/pmax)<pre>
315 # <b>mount -o ro,-r=1024,-w=1024,-U,-3 my.server.com:/tftpboot /mnt</b>
316 or
317 # <b>mount my.server.com:/tftpboot /mnt</b>
318 </pre>
319 If you don't supply the read and write sizes, there is a risk
320 that the default values are too large. The emulator currently
321 does not handle fragmentation/defragmentation of <i>outgoing</i>
322 packets, so going above the ethernet frame size (1518) is a very
323 bad idea. Incoming packets (reading from nfs) should work, though,
324 for example during an NFS install.
325 </ul>
326
327 The example above uses read-only mounts. That is enough for things like
328 letting NetBSD/pmax or OpenBSD/pmax install via NFS, without the need for
329 a CDROM ISO image. You can use a read-write mount if you wish to share
330 files in both directions, but then you should be aware of the
331 fragmentation issue mentioned above.
332
333 <p>TODO: Write a section on how to connect multiple emulator instances.
334 (Using the <tt>local_port</tt> and <tt>add_remote</tt> configuration file
335 commands.)
336
337
338
339
340
341
342 <p><br>
343 <a name="devices"></a>
344 <h3>Emulation of hardware devices</h3>
345
346 Each file in the <tt>device/</tt> directory is responsible for one
347 hardware device. These are used from <tt>src/machine.c</tt>, when
348 initializing which hardware a particular machine model will be using, or
349 when adding devices to a machine using the <tt>device()</tt> command in
350 configuration files.
351
352 <p><font color="#ff0000">NOTE: The device registry subsystem is currently
353 in a state of flux, as it is being redesigned.</font>
354
355 <p>(I'll be using the name "<tt>foo</tt>" as the name of the device in all
356 these examples. This is pseudo code, it might need some modification to
357 actually compile and run.)
358
359 <p>Each device should have the following:
360
361 <p>
362 <ul>
363 <li>A <tt>devinit</tt> function in <tt>src/devices/dev_foo.c</tt>. It
364 would typically look something like this:
365 <pre>
366 /*
367 * devinit_foo():
368 */
369 int devinit_foo(struct devinit *devinit)
370 {
371 struct foo_data *d = malloc(sizeof(struct foo_data));
372
373 if (d == NULL) {
374 fprintf(stderr, "out of memory\n");
375 exit(1);
376 }
377 memset(d, 0, sizeof(struct foon_data));
378
379 /*
380 * Set up stuff here, for example fill d with useful
381 * data. devinit contains settings like address, irq_nr,
382 * and other things.
383 *
384 * ...
385 */
386
387 memory_device_register(devinit->machine->memory, devinit->name,
388 devinit->addr, DEV_FOO_LENGTH,
389 dev_foo_access, (void *)d, MEM_DEFAULT, NULL);
390
391 /* This should only be here if the device
392 has a tick function: */
393 machine_add_tickfunction(machine, dev_foo_tick, d,
394 FOO_TICKSHIFT);
395
396 /* Return 1 if the device was successfully added. */
397 return 1;
398 }
399 </pre><br>
400
401 <li>At the top of <tt>dev_foo.c</tt>, the <tt>foo_data</tt> struct
402 should be defined.
403 <pre>
404 struct foo_data {
405 int irq_nr;
406 /* ... */
407 }
408 </pre><br>
409
410 <li>If <tt>foo</tt> has a tick function (that is, something that needs to be
411 run at regular intervals) then <tt>FOO_TICKSHIFT</tt> and a tick
412 function need to be defined as well:
413 <pre>
414 #define FOO_TICKSHIFT 10
415
416 void dev_foo_tick(struct cpu *cpu, void *extra)
417 {
418 struct foo_data *d = (struct foo_data *) extra;
419
420 if (.....)
421 cpu_interrupt(cpu, d->irq_nr);
422 else
423 cpu_interrupt_ack(cpu, d->irq_nr);
424 }
425 </pre><br>
426
427 <li>And last but not least, the device should have an access function.
428 The access function is called whenever there is a load or store
429 to an address which is in the device' memory mapped region.
430 <pre>
431 int dev_foo_access(struct cpu *cpu, struct memory *mem,
432 uint64_t relative_addr, unsigned char *data, size_t len,
433 int writeflag, void *extra)
434 {
435 struct foo_data *d = extra;
436 uint64_t idata = 0, odata = 0;
437
438 idata = memory_readmax64(cpu, data, len);
439 switch (relative_addr) {
440 /* .... */
441 }
442
443 if (writeflag == MEM_READ)
444 memory_writemax64(cpu, data, len, odata);
445
446 /* Perhaps interrupts need to be asserted or
447 deasserted: */
448 dev_foo_tick(cpu, extra);
449
450 /* Return successfully. */
451 return 1;
452 }
453 </pre><br>
454 </ul>
455
456 <p>
457 The return value of the access function has until 2004-07-02 been a
458 true/false value; 1 for success, or 0 for device access failure. A device
459 access failure (on MIPS) will result in a DBE exception.
460
461 <p>
462 Some devices are converted to support arbitrary memory latency
463 values. The return value is the number of cycles that the read or
464 write access took. A value of 1 means one cycle, a value of 10 means 10
465 cycles. Negative values are used for device access failures, and the
466 absolute value of the value is then the number of cycles; a value of -5
467 means that the access failed, and took 5 cycles.
468
469 <p>
470 To be compatible with pre-20040702 devices, a return value of 0 is treated
471 by the caller (in <tt>src/memory_rw.c</tt>) as a value of -1.
472
473
474
475
476
477 <p><br>
478 <a name="regtest"></a>
479 <h3>Regression tests</h3>
480
481 In order to make sure that the emulator actually works like it is supposed
482 to, it must be tested. For this purpose, there is a simple regression
483 testing framework in the <tt>tests/</tt> directory.
484
485 <p>
486 <i>NOTE: The regression testing framework is basically just a skeleton so far.
487 Regression tests are very good to have. However, the fact that complete
488 operating systems can run in the emulator indicate that the emulation is
489 probably not too incorrect. This makes it less of a priority to write
490 regression tests.</i>
491
492 <p>
493 To run all the regression tests, type <tt>make regtest</tt>. Each assembly
494 language file matching the pattern <tt>test_*.S</tt> will be compiled and
495 linked into a 64-bit MIPS ELF (using a gcc cross compiler), and run in the
496 emulator. If everything goes well, you should see something like this:
497
498 <pre>
499 $ make regtest
500 cd tests; make run_tests; cd ..
501 gcc33 -Wall -fomit-frame-pointer -fmove-all-movables -fpeephole -O2
502 -mcpu=ev5 -I/usr/X11R6/include -lm -L/usr/X11R6/lib -lX11 do_tests.c
503 -o do_tests
504 do_tests.c: In function `main':
505 do_tests.c:173: warning: unused variable `s'
506 /var/tmp//ccFOupvD.o: In function `do_tests':
507 /var/tmp//ccFOupvD.o(.text+0x3a8): warning: tmpnam() possibly used
508 unsafely; consider using mkstemp()
509 mips64-unknown-elf-gcc -g -O3 -fno-builtin -fschedule-insns -mips64
510 -mabi=64 test_common.c -c -o test_common.o
511 ./do_tests "mips64-unknown-elf-gcc -g -O3 -fno-builtin -fschedule-insns
512 -mips64 -mabi=64" "mips64-unknown-elf-as -mabi=64 -mips64"
513 "mips64-unknown-elf-ld -Ttext 0xa800000000030000 -e main
514 --oformat=elf64-bigmips" "../gxemul"
515
516 Starting tests:
517 test_addu.S (-a)
518 test_addu.S (-a -b)
519 test_clo_clz.S (-a)
520 test_clo_clz.S (-a -b)
521 ..
522 test_unaligned.S (-a)
523 test_unaligned.S (-a -b)
524
525 Done. (12 tests done)
526 PASS: 12
527 FAIL: 0
528
529 ----------------
530
531 All tests OK
532
533 ----------------
534 </pre>
535
536 <p>
537 Each test writes output to stdout, and there is a <tt>test_*.good</tt> for
538 each <tt>.S</tt> file which contains the wanted output. If the actual
539 output matches the <tt>.good</tt> file, then the test passes, otherwise it
540 fails.
541
542 <p>
543 Read <tt>tests/README</tt> for more information.
544
545
546
547
548 </body>
549 </html>

  ViewVC Help
Powered by ViewVC 1.1.26