blob: 61f79e3be321cb6699207b7c27cd624b68059d37 [file] [log] [blame]
sewardj267100d2005-04-24 12:33:12 +00001
njnd01fef72005-03-25 23:35:48 +00002/*--------------------------------------------------------------------*/
sewardj267100d2005-04-24 12:33:12 +00003/*--- Store and compare stack backtraces m_execontext.c ---*/
njnd01fef72005-03-25 23:35:48 +00004/*--------------------------------------------------------------------*/
5
6/*
7 This file is part of Valgrind, a dynamic binary instrumentation
8 framework.
9
sewardj03f8d3f2012-08-05 15:46:46 +000010 Copyright (C) 2000-2012 Julian Seward
njnd01fef72005-03-25 23:35:48 +000011 jseward@acm.org
12
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License as
15 published by the Free Software Foundation; either version 2 of the
16 License, or (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
26 02111-1307, USA.
27
28 The GNU General Public License is contained in the file COPYING.
29*/
30
njnc7561b92005-06-19 01:24:32 +000031#include "pub_core_basics.h"
sewardj9877e9b2007-08-23 10:24:30 +000032#include "pub_core_debuglog.h"
njn132bfcc2005-06-04 19:16:06 +000033#include "pub_core_libcassert.h"
njn24a6efb2005-06-20 03:36:51 +000034#include "pub_core_libcprint.h" // For VG_(message)()
njnaf1d7df2005-06-11 01:31:52 +000035#include "pub_core_mallocfree.h"
njn20242342005-05-16 23:31:24 +000036#include "pub_core_options.h"
njnd0d7c1f2005-06-21 00:33:19 +000037#include "pub_core_stacktrace.h"
sewardj3059d272007-12-21 01:24:59 +000038#include "pub_core_machine.h" // VG_(get_IP)
39#include "pub_core_vki.h" // To keep pub_core_threadstate.h happy
sewardj6c591e12011-04-11 16:17:51 +000040#include "pub_core_libcsetjmp.h" // Ditto
sewardj3059d272007-12-21 01:24:59 +000041#include "pub_core_threadstate.h" // VG_(is_valid_tid)
42#include "pub_core_execontext.h" // self
njnd01fef72005-03-25 23:35:48 +000043
44/*------------------------------------------------------------*/
45/*--- Low-level ExeContext storage. ---*/
46/*------------------------------------------------------------*/
47
sewardj9877e9b2007-08-23 10:24:30 +000048/* The first 4 IP values are used in comparisons to remove duplicate
49 errors, and for comparing against suppression specifications. The
50 rest are purely informational (but often important).
51
52 The contexts are stored in a traditional chained hash table, so as
53 to allow quick determination of whether a new context already
54 exists. The hash table starts small and expands dynamically, so as
55 to keep the load factor below 1.0.
56
57 The idea is only to ever store any one context once, so as to save
58 space and make exact comparisons faster. */
59
60
61/* Primes for the hash table */
62
63#define N_EC_PRIMES 18
64
65static SizeT ec_primes[N_EC_PRIMES] = {
66 769UL, 1543UL, 3079UL, 6151UL,
67 12289UL, 24593UL, 49157UL, 98317UL,
68 196613UL, 393241UL, 786433UL, 1572869UL,
69 3145739UL, 6291469UL, 12582917UL, 25165843UL,
70 50331653UL, 100663319UL
71};
72
73
74/* Each element is present in a hash chain, and also contains a
75 variable length array of guest code addresses (the useful part). */
njnd01fef72005-03-25 23:35:48 +000076
77struct _ExeContext {
sewardj9877e9b2007-08-23 10:24:30 +000078 struct _ExeContext* chain;
sewardj7cf4e6b2008-05-01 20:24:26 +000079 /* A 32-bit unsigned integer that uniquely identifies this
80 ExeContext. Memcheck uses these for origin tracking. Values
81 must be nonzero (else Memcheck's origin tracking is hosed), must
82 be a multiple of four, and must be unique. Hence they start at
83 4. */
84 UInt ecu;
njnc0ec8e92005-12-25 06:34:04 +000085 /* Variable-length array. The size is 'n_ips'; at
njnd01fef72005-03-25 23:35:48 +000086 least 1, at most VG_DEEPEST_BACKTRACE. [0] is the current IP,
87 [1] is its caller, [2] is the caller of [1], etc. */
sewardj7cf4e6b2008-05-01 20:24:26 +000088 UInt n_ips;
njnd01fef72005-03-25 23:35:48 +000089 Addr ips[0];
90};
91
njnd01fef72005-03-25 23:35:48 +000092
sewardj9877e9b2007-08-23 10:24:30 +000093/* This is the dynamically expanding hash table. */
94static ExeContext** ec_htab; /* array [ec_htab_size] of ExeContext* */
95static SizeT ec_htab_size; /* one of the values in ec_primes */
96static SizeT ec_htab_size_idx; /* 0 .. N_EC_PRIMES-1 */
njnd01fef72005-03-25 23:35:48 +000097
sewardj7cf4e6b2008-05-01 20:24:26 +000098/* ECU serial number */
99static UInt ec_next_ecu = 4; /* We must never issue zero */
100
njnd01fef72005-03-25 23:35:48 +0000101
102/* Stats only: the number of times the system was searched to locate a
103 context. */
njn0fd92f42005-10-06 03:32:42 +0000104static ULong ec_searchreqs;
njnd01fef72005-03-25 23:35:48 +0000105
106/* Stats only: the number of full context comparisons done. */
njn0fd92f42005-10-06 03:32:42 +0000107static ULong ec_searchcmps;
njnd01fef72005-03-25 23:35:48 +0000108
109/* Stats only: total number of stored contexts. */
njn0fd92f42005-10-06 03:32:42 +0000110static ULong ec_totstored;
njnd01fef72005-03-25 23:35:48 +0000111
112/* Number of 2, 4 and (fast) full cmps done. */
njn0fd92f42005-10-06 03:32:42 +0000113static ULong ec_cmp2s;
114static ULong ec_cmp4s;
115static ULong ec_cmpAlls;
njnd01fef72005-03-25 23:35:48 +0000116
117
118/*------------------------------------------------------------*/
119/*--- Exported functions. ---*/
120/*------------------------------------------------------------*/
121
122
123/* Initialise this subsystem. */
124static void init_ExeContext_storage ( void )
125{
126 Int i;
127 static Bool init_done = False;
sewardj7cf4e6b2008-05-01 20:24:26 +0000128 if (LIKELY(init_done))
njnd01fef72005-03-25 23:35:48 +0000129 return;
130 ec_searchreqs = 0;
131 ec_searchcmps = 0;
132 ec_totstored = 0;
133 ec_cmp2s = 0;
134 ec_cmp4s = 0;
135 ec_cmpAlls = 0;
sewardj9877e9b2007-08-23 10:24:30 +0000136
137 ec_htab_size_idx = 0;
138 ec_htab_size = ec_primes[ec_htab_size_idx];
sewardj9c606bd2008-09-18 18:12:50 +0000139 ec_htab = VG_(arena_malloc)(VG_AR_EXECTXT, "execontext.iEs1",
sewardj9877e9b2007-08-23 10:24:30 +0000140 sizeof(ExeContext*) * ec_htab_size);
141 for (i = 0; i < ec_htab_size; i++)
142 ec_htab[i] = NULL;
143
njnd01fef72005-03-25 23:35:48 +0000144 init_done = True;
145}
146
147
148/* Print stats. */
149void VG_(print_ExeContext_stats) ( void )
150{
151 init_ExeContext_storage();
152 VG_(message)(Vg_DebugMsg,
sewardj738856f2009-07-15 14:48:32 +0000153 " exectx: %'lu lists, %'llu contexts (avg %'llu per list)\n",
sewardja8ffda62008-07-18 18:23:24 +0000154 ec_htab_size, ec_totstored, ec_totstored / (ULong)ec_htab_size
njnd01fef72005-03-25 23:35:48 +0000155 );
156 VG_(message)(Vg_DebugMsg,
sewardj738856f2009-07-15 14:48:32 +0000157 " exectx: %'llu searches, %'llu full compares (%'llu per 1000)\n",
njnd01fef72005-03-25 23:35:48 +0000158 ec_searchreqs, ec_searchcmps,
159 ec_searchreqs == 0
sewardja8ffda62008-07-18 18:23:24 +0000160 ? 0ULL
161 : ( (ec_searchcmps * 1000ULL) / ec_searchreqs )
njnd01fef72005-03-25 23:35:48 +0000162 );
163 VG_(message)(Vg_DebugMsg,
sewardj738856f2009-07-15 14:48:32 +0000164 " exectx: %'llu cmp2, %'llu cmp4, %'llu cmpAll\n",
njnd01fef72005-03-25 23:35:48 +0000165 ec_cmp2s, ec_cmp4s, ec_cmpAlls
166 );
167}
168
169
170/* Print an ExeContext. */
171void VG_(pp_ExeContext) ( ExeContext* ec )
172{
njnc0ec8e92005-12-25 06:34:04 +0000173 VG_(pp_StackTrace)( ec->ips, ec->n_ips );
njnd01fef72005-03-25 23:35:48 +0000174}
175
176
njnb7a4e2e2009-05-01 00:30:43 +0000177/* Compare two ExeContexts. Number of callers considered depends on res. */
njnd01fef72005-03-25 23:35:48 +0000178Bool VG_(eq_ExeContext) ( VgRes res, ExeContext* e1, ExeContext* e2 )
179{
njnc0ec8e92005-12-25 06:34:04 +0000180 Int i;
181
njnd01fef72005-03-25 23:35:48 +0000182 if (e1 == NULL || e2 == NULL)
183 return False;
njnc0ec8e92005-12-25 06:34:04 +0000184
185 // Must be at least one address in each trace.
186 tl_assert(e1->n_ips >= 1 && e2->n_ips >= 1);
187
njnd01fef72005-03-25 23:35:48 +0000188 switch (res) {
189 case Vg_LowRes:
190 /* Just compare the top two callers. */
191 ec_cmp2s++;
njnc0ec8e92005-12-25 06:34:04 +0000192 for (i = 0; i < 2; i++) {
193 if ( (e1->n_ips <= i) && (e2->n_ips <= i)) return True;
194 if ( (e1->n_ips <= i) && !(e2->n_ips <= i)) return False;
195 if (!(e1->n_ips <= i) && (e2->n_ips <= i)) return False;
196 if (e1->ips[i] != e2->ips[i]) return False;
197 }
njnd01fef72005-03-25 23:35:48 +0000198 return True;
199
200 case Vg_MedRes:
201 /* Just compare the top four callers. */
202 ec_cmp4s++;
njnc0ec8e92005-12-25 06:34:04 +0000203 for (i = 0; i < 4; i++) {
204 if ( (e1->n_ips <= i) && (e2->n_ips <= i)) return True;
205 if ( (e1->n_ips <= i) && !(e2->n_ips <= i)) return False;
206 if (!(e1->n_ips <= i) && (e2->n_ips <= i)) return False;
207 if (e1->ips[i] != e2->ips[i]) return False;
208 }
njnd01fef72005-03-25 23:35:48 +0000209 return True;
210
211 case Vg_HighRes:
212 ec_cmpAlls++;
213 /* Compare them all -- just do pointer comparison. */
214 if (e1 != e2) return False;
215 return True;
216
217 default:
218 VG_(core_panic)("VG_(eq_ExeContext): unrecognised VgRes");
219 }
220}
221
sewardj9877e9b2007-08-23 10:24:30 +0000222/* VG_(record_ExeContext) is the head honcho here. Take a snapshot of
223 the client's stack. Search our collection of ExeContexts to see if
224 we already have it, and if not, allocate a new one. Either way,
225 return a pointer to the context. If there is a matching context we
njnd01fef72005-03-25 23:35:48 +0000226 guarantee to not allocate a new one. Thus we never store
227 duplicates, and so exact equality can be quickly done as equality
228 on the returned ExeContext* values themselves. Inspired by Hugs's
sewardj9877e9b2007-08-23 10:24:30 +0000229 Text type.
230
231 Also checks whether the hash table needs expanding, and expands it
232 if so. */
233
sewardjbb2e09f2006-10-24 21:43:38 +0000234static inline UWord ROLW ( UWord w, Int n )
235{
236 Int bpw = 8 * sizeof(UWord);
237 w = (w << n) | (w >> (bpw-n));
238 return w;
239}
240
sewardj9877e9b2007-08-23 10:24:30 +0000241static UWord calc_hash ( Addr* ips, UInt n_ips, UWord htab_sz )
242{
243 UInt i;
244 UWord hash = 0;
245 vg_assert(htab_sz > 0);
246 for (i = 0; i < n_ips; i++) {
247 hash ^= ips[i];
248 hash = ROLW(hash, 19);
249 }
250 return hash % htab_sz;
251}
252
253static void resize_ec_htab ( void )
254{
255 SizeT i;
256 SizeT new_size;
257 ExeContext** new_ec_htab;
258
259 vg_assert(ec_htab_size_idx >= 0 && ec_htab_size_idx < N_EC_PRIMES);
260 if (ec_htab_size_idx == N_EC_PRIMES-1)
261 return; /* out of primes - can't resize further */
262
263 new_size = ec_primes[ec_htab_size_idx + 1];
sewardj9c606bd2008-09-18 18:12:50 +0000264 new_ec_htab = VG_(arena_malloc)(VG_AR_EXECTXT, "execontext.reh1",
sewardj9877e9b2007-08-23 10:24:30 +0000265 sizeof(ExeContext*) * new_size);
266
267 VG_(debugLog)(
268 1, "execontext",
269 "resizing htab from size %lu to %lu (idx %lu) Total#ECs=%llu\n",
270 ec_htab_size, new_size, ec_htab_size_idx + 1, ec_totstored);
271
272 for (i = 0; i < new_size; i++)
273 new_ec_htab[i] = NULL;
274
275 for (i = 0; i < ec_htab_size; i++) {
276 ExeContext* cur = ec_htab[i];
277 while (cur) {
278 ExeContext* next = cur->chain;
279 UWord hash = calc_hash(cur->ips, cur->n_ips, new_size);
280 vg_assert(hash < new_size);
281 cur->chain = new_ec_htab[hash];
282 new_ec_htab[hash] = cur;
283 cur = next;
284 }
285 }
286
287 VG_(arena_free)(VG_AR_EXECTXT, ec_htab);
288 ec_htab = new_ec_htab;
289 ec_htab_size = new_size;
290 ec_htab_size_idx++;
291}
292
sewardj7cf4e6b2008-05-01 20:24:26 +0000293/* Do the first part of getting a stack trace: actually unwind the
294 stack, and hand the results off to the duplicate-trace-finder
295 (_wrk2). */
296static ExeContext* record_ExeContext_wrk2 ( Addr* ips, UInt n_ips ); /*fwds*/
sewardj3059d272007-12-21 01:24:59 +0000297static ExeContext* record_ExeContext_wrk ( ThreadId tid, Word first_ip_delta,
298 Bool first_ip_only )
njnd01fef72005-03-25 23:35:48 +0000299{
florian7711f9e2012-06-29 21:20:52 +0000300 Addr ips[VG_(clo_backtrace_size)];
sewardj7cf4e6b2008-05-01 20:24:26 +0000301 UInt n_ips;
sewardjbb2e09f2006-10-24 21:43:38 +0000302
sewardj7cf4e6b2008-05-01 20:24:26 +0000303 init_ExeContext_storage();
sewardjbb2e09f2006-10-24 21:43:38 +0000304
305 vg_assert(sizeof(void*) == sizeof(UWord));
306 vg_assert(sizeof(void*) == sizeof(Addr));
njnd01fef72005-03-25 23:35:48 +0000307
sewardj7cf4e6b2008-05-01 20:24:26 +0000308 vg_assert(VG_(is_valid_tid)(tid));
309
sewardj3059d272007-12-21 01:24:59 +0000310 if (first_ip_only) {
sewardj3059d272007-12-21 01:24:59 +0000311 n_ips = 1;
florian78b83ae2012-07-26 02:14:28 +0000312 ips[0] = VG_(get_IP)(tid) + first_ip_delta;
sewardj3059d272007-12-21 01:24:59 +0000313 } else {
314 n_ips = VG_(get_StackTrace)( tid, ips, VG_(clo_backtrace_size),
sewardjb8b79ad2008-03-03 01:35:41 +0000315 NULL/*array to dump SP values in*/,
316 NULL/*array to dump FP values in*/,
sewardj3059d272007-12-21 01:24:59 +0000317 first_ip_delta );
318 }
319
njn3a4b58f2009-05-07 23:08:10 +0000320 return record_ExeContext_wrk2 ( ips, n_ips );
sewardj7cf4e6b2008-05-01 20:24:26 +0000321}
322
323/* Do the second part of getting a stack trace: ips[0 .. n_ips-1]
324 holds a proposed trace. Find or allocate a suitable ExeContext.
325 Note that callers must have done init_ExeContext_storage() before
326 getting to this point. */
327static ExeContext* record_ExeContext_wrk2 ( Addr* ips, UInt n_ips )
328{
329 Int i;
330 Bool same;
331 UWord hash;
332 ExeContext* new_ec;
333 ExeContext* list;
334 ExeContext *prev2, *prev;
335
336 static UInt ctr = 0;
337
sewardj9877e9b2007-08-23 10:24:30 +0000338 tl_assert(n_ips >= 1 && n_ips <= VG_(clo_backtrace_size));
njnd01fef72005-03-25 23:35:48 +0000339
340 /* Now figure out if we've seen this one before. First hash it so
341 as to determine the list number. */
sewardj9877e9b2007-08-23 10:24:30 +0000342 hash = calc_hash( ips, n_ips, ec_htab_size );
njnd01fef72005-03-25 23:35:48 +0000343
sewardj9877e9b2007-08-23 10:24:30 +0000344 /* And (the expensive bit) look a for matching entry in the list. */
njnd01fef72005-03-25 23:35:48 +0000345
346 ec_searchreqs++;
347
sewardjbb2e09f2006-10-24 21:43:38 +0000348 prev2 = NULL;
349 prev = NULL;
sewardj9877e9b2007-08-23 10:24:30 +0000350 list = ec_htab[hash];
njnd01fef72005-03-25 23:35:48 +0000351
352 while (True) {
353 if (list == NULL) break;
354 ec_searchcmps++;
355 same = True;
njnc0ec8e92005-12-25 06:34:04 +0000356 for (i = 0; i < n_ips; i++) {
njnd01fef72005-03-25 23:35:48 +0000357 if (list->ips[i] != ips[i]) {
358 same = False;
359 break;
360 }
361 }
362 if (same) break;
sewardjbb2e09f2006-10-24 21:43:38 +0000363 prev2 = prev;
364 prev = list;
sewardj9877e9b2007-08-23 10:24:30 +0000365 list = list->chain;
njnd01fef72005-03-25 23:35:48 +0000366 }
367
368 if (list != NULL) {
sewardjbb2e09f2006-10-24 21:43:38 +0000369 /* Yay! We found it. Once every 8 searches, move it one step
370 closer to the start of the list to make future searches
371 cheaper. */
sewardj9877e9b2007-08-23 10:24:30 +0000372 if (0 == ((ctr++) & 7)) {
373 if (prev2 != NULL && prev != NULL) {
374 /* Found at 3rd or later position in the chain. */
375 vg_assert(prev2->chain == prev);
376 vg_assert(prev->chain == list);
377 prev2->chain = list;
378 prev->chain = list->chain;
379 list->chain = prev;
380 }
381 else if (prev2 == NULL && prev != NULL) {
382 /* Found at 2nd position in the chain. */
383 vg_assert(ec_htab[hash] == prev);
384 vg_assert(prev->chain == list);
385 prev->chain = list->chain;
386 list->chain = prev;
387 ec_htab[hash] = list;
388 }
sewardjbb2e09f2006-10-24 21:43:38 +0000389 }
njnd01fef72005-03-25 23:35:48 +0000390 return list;
391 }
392
393 /* Bummer. We have to allocate a new context record. */
394 ec_totstored++;
395
sewardj9c606bd2008-09-18 18:12:50 +0000396 new_ec = VG_(arena_malloc)( VG_AR_EXECTXT, "execontext.rEw2.2",
njnc0ec8e92005-12-25 06:34:04 +0000397 sizeof(struct _ExeContext)
398 + n_ips * sizeof(Addr) );
njnd01fef72005-03-25 23:35:48 +0000399
njnc0ec8e92005-12-25 06:34:04 +0000400 for (i = 0; i < n_ips; i++)
njnd01fef72005-03-25 23:35:48 +0000401 new_ec->ips[i] = ips[i];
402
sewardj7cf4e6b2008-05-01 20:24:26 +0000403 vg_assert(VG_(is_plausible_ECU)(ec_next_ecu));
404 new_ec->ecu = ec_next_ecu;
405 ec_next_ecu += 4;
406 if (ec_next_ecu == 0) {
407 /* Urr. Now we're hosed; we emitted 2^30 ExeContexts already
408 and have run out of numbers. Not sure what to do. */
409 VG_(core_panic)("m_execontext: more than 2^30 ExeContexts created");
410 }
411
njnc0ec8e92005-12-25 06:34:04 +0000412 new_ec->n_ips = n_ips;
sewardj9877e9b2007-08-23 10:24:30 +0000413 new_ec->chain = ec_htab[hash];
414 ec_htab[hash] = new_ec;
415
416 /* Resize the hash table, maybe? */
417 if ( ((ULong)ec_totstored) > ((ULong)ec_htab_size) ) {
418 vg_assert(ec_htab_size_idx >= 0 && ec_htab_size_idx < N_EC_PRIMES);
419 if (ec_htab_size_idx < N_EC_PRIMES-1)
420 resize_ec_htab();
421 }
njnd01fef72005-03-25 23:35:48 +0000422
njnd01fef72005-03-25 23:35:48 +0000423 return new_ec;
424}
425
sewardj3059d272007-12-21 01:24:59 +0000426ExeContext* VG_(record_ExeContext)( ThreadId tid, Word first_ip_delta ) {
427 return record_ExeContext_wrk( tid, first_ip_delta,
428 False/*!first_ip_only*/ );
429}
430
florianbb4f5da2012-07-23 15:40:41 +0000431ExeContext* VG_(record_depth_1_ExeContext)( ThreadId tid, Word first_ip_delta )
432{
433 return record_ExeContext_wrk( tid, first_ip_delta,
sewardj3059d272007-12-21 01:24:59 +0000434 True/*first_ip_only*/ );
435}
436
sewardj7cf4e6b2008-05-01 20:24:26 +0000437ExeContext* VG_(make_depth_1_ExeContext_from_Addr)( Addr a ) {
438 init_ExeContext_storage();
439 return record_ExeContext_wrk2( &a, 1 );
440}
sewardj3059d272007-12-21 01:24:59 +0000441
sewardj7cf4e6b2008-05-01 20:24:26 +0000442StackTrace VG_(get_ExeContext_StackTrace) ( ExeContext* e ) {
njnd01fef72005-03-25 23:35:48 +0000443 return e->ips;
444}
445
sewardj7cf4e6b2008-05-01 20:24:26 +0000446UInt VG_(get_ECU_from_ExeContext)( ExeContext* e ) {
447 vg_assert(VG_(is_plausible_ECU)(e->ecu));
448 return e->ecu;
449}
450
451Int VG_(get_ExeContext_n_ips)( ExeContext* e ) {
452 vg_assert(e->n_ips >= 1);
453 return e->n_ips;
454}
455
456ExeContext* VG_(get_ExeContext_from_ECU)( UInt ecu )
457{
458 UWord i;
459 ExeContext* ec;
460 vg_assert(VG_(is_plausible_ECU)(ecu));
461 vg_assert(ec_htab_size > 0);
462 for (i = 0; i < ec_htab_size; i++) {
463 for (ec = ec_htab[i]; ec; ec = ec->chain) {
464 if (ec->ecu == ecu)
465 return ec;
466 }
467 }
468 return NULL;
469}
470
sewardjf98e1c02008-10-25 16:22:41 +0000471ExeContext* VG_(make_ExeContext_from_StackTrace)( Addr* ips, UInt n_ips )
472{
473 return record_ExeContext_wrk2(ips, n_ips);
474}
475
njnd01fef72005-03-25 23:35:48 +0000476/*--------------------------------------------------------------------*/
sewardj267100d2005-04-24 12:33:12 +0000477/*--- end m_execontext.c ---*/
njnd01fef72005-03-25 23:35:48 +0000478/*--------------------------------------------------------------------*/