utoddl@ecsvax.UUCP (Todd M. Lewis) (07/28/89)
I have a program which seems to use memory it doesn't own from time to time, and I'd like some way to catch it in the act. I've been thinking about a method to see if/when it messes up free memory, and I'd like to know why this wouldn't work: * Zap FreeMem() to zero out memory whenever it is freed. * Zero out all memory which is already freed. * Periodically scan all free memory to see if it is still zero'd out. If it isn't, report the address range that has been munged (so you know about it) and optionally zero that RAM (and see who dies:). Because of the overhead this would put on the system you probably wouldn't want to run it all the time, but during software development and testing this could be handy to have around. It wouldn't catch writing over another program's data/code, but it could catch some illegal uses of free RAM. What I'd like to know is: 1 Would this work? What are the pitfalls of this method? 2 Has it already been done? 3 Would anybody else be interested it this? Todd M. Lewis, utoddl@ecsvax.uncecs.edu, utoddl@ecsvax.bitnet To quote Eugene H. Spafford, "Crisis and Aftermath", Communications of the ACM, vol. 32, no. 6, (June 1989), p. 684: "It is curious that this many people [...] would assume to know the intent of the author based on the observed behavior of the program."
usenet@cps3xx.UUCP (Usenet file owner) (07/28/89)
In article <7408@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) writes: > >I have a program which seems to use memory it doesn't own >from time to time, and I'd like some way to catch it in the act. >I've been thinking about a method to see if/when it messes up free >memory, and I'd like to know why this wouldn't work: > > * Zap FreeMem() to zero out memory whenever it is freed. > * Zero out all memory which is already freed. > * Periodically scan all free memory to see if it is still > zero'd out. If it isn't, report the address range that > has been munged (so you know about it) and optionally > zero that RAM (and see who dies:). SOunds good. Easy way to Zap FreeMem is make a function called "freemem" and then for every other module in the program, #define FreeMem freemem To help solve my memory trashing problems, I have sometimes replace AllocMem & FreeMem with the above method with routines that did the same, but tracked each Alloc and Free. The FreeMem routine would print an error (with name of the function that called it) if something tried to free an unalloced block or a block of the wrong size. TO help debugging without rebooting, then you can run thru your memory alloc list and actually free blocks that did not otherwise get freed. If your memory mismangement is more complicated than messing up Alloc/Free, then I suggest as a first cut to just check memory that you have used then freed. REAL NAME: Joe Porkka jap@frith.cl.msu.edu
utoddl@ecsvax.UUCP (Todd M. Lewis) (07/28/89)
In article <3965@cps3xx.UUCP>, usenet@cps3xx.UUCP (Usenet file owner) writes: > In article <7408@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) writes: > > > >I have a program which seems to use memory it doesn't own > >from time to time, and I'd like some way to catch it in the act. > >I've been thinking about a method to see if/when it messes up free > >memory, and I'd like to know why this wouldn't work: > > SOunds good. > > Easy way to Zap FreeMem is make a function called "freemem" > and then for every other module in the program, #define FreeMem freemem Perhaps I have misled you: I didn't write the program in question, nor do I have the source. I'm not even 100% absolutely sure which program it is. The failure modes make me think that some program is using memory it doesn't own. Sometimes it stomps on another program (or even itself), sometimes it stomps on free memory. I can't watch allocated RAM (heck, some of that stuff is _supposed_ to change) but free memory could be monitored. Actually, what I had in mind was to do a SetFunction() on FreeMem() so I could monitor ALL the free memory in the system, not just for programs I may be writing. I would really like to run this when I'm trying out other peoples programs (not than anyone reading this would write programs with bugs:). > > To help solve my memory trashing problems, I have sometimes replace > AllocMem & FreeMem with the above method with routines > that did the same, but tracked each Alloc and Free. The FreeMem > routine would print an error (with name of the function that called it) > if something tried to free an unalloced block or a block of the > wrong size. TO help debugging without rebooting, then you can > run thru your memory alloc list and actually free blocks that > did not otherwise get freed. That sounds like a fine solution, but to a different problem. > > REAL NAME: Joe Porkka jap@frith.cl.msu.edu (Long signature follows so posting will work. You can tell it is my signature by the way I slant my '/'s. ) _____ | Todd M. Lewis Disclaimer: If you want my employer's ||\/| utoddl@ecsvax.uncecs.edu ideas, you'll have to || || utoddl@ecsvax.bitnet _buy_ them. | || |___ (Never write a program bigger than your screen.)
fgd3@jc3b21.UUCP (Fabbian G. Dufoe) (07/29/89)
In article <7408@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) asks about
clearing free memory periodically as part of an effort to catch programs
which use memory they don't own. This would work so long as you keep two
things in mind.
(1) The pointer to the next chunk of free memory occupies the first
eight bytes of the current chunk. You can't free that.
(2) If a program is writing on memory it hasn't allocated it may have
clobbered one of those pointers. Consquently, you can't walk the
system memory free list with any confidence.
Those points aside, you could check whatever memory a chunk contains beyond
the next chunk pointer to be sure that was zero. However, any application
might allocate the memory, write on it, and free it without clearing it.
So finding non-zero values in free memory wouldn't mean you had discovered
a program which was writing on memory it didn't own.
--Fabbian Dufoe
350 Ling-A-Mor Terrace South
St. Petersburg, Florida 33705
813-823-2350
UUCP: ...uunet!pdn!jc3b21!fgd3
shadow@pawl.rpi.edu (Deven T. Corzine) (07/29/89)
On 28 Jul 89 19:44:05 GMT, fgd3@jc3b21.UUCP (Fabbian G. Dufoe) said: FGD> In article <7408@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) FGD> asks about clearing free memory periodically as part of an effort FGD> to catch programs which use memory they don't own. This would FGD> work so long as you keep two things in mind. FGD> (1) The pointer to the next chunk of free memory occupies FGD> the first eight bytes of the current chunk. You can't free that. It IS free. You can't *zero* that. FGD> (2) If a program is writing on memory it hasn't allocated it FGD> may have clobbered one of those pointers. Consquently, you can't FGD> walk the system memory free list with any confidence. If it has clobbered the free list, the system is in a corrupt state anyhow, so you will lose anyhow. FGD> Those points aside, you could check whatever memory a chunk FGD> contains beyond the next chunk pointer to be sure that was zero. FGD> However, any application might allocate the memory, write on it, FGD> and free it without clearing it. So finding non-zero values in FGD> free memory wouldn't mean you had discovered a program which was FGD> writing on memory it didn't own. You missed the point entirely. The idea was to SetFunction() the FreeMem() function to zero memory *as it is freed*. Then either a SetFunction()ed AllocMem() function or some separate task/function could check the free list to make sure it is zeroed. If not, it is a sign of corruption. I think it's a fine idea, by the way. [we still need resource tracking...] Deven -- Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750 Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven Simple things should be simple and complex things should be possible.
nichiren@glyph.UUCP (Andy Heffernan) (07/30/89)
In article <7413@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) writes: >Actually, what I had in mind was to do a SetFunction() on >FreeMem() so I could monitor ALL the free memory in the system, >not just for programs I may be writing. I would really like >to run this when I'm trying out other peoples programs (not than >anyone reading this would write programs with bugs:). Sound like you could use Snoop, which probably is still on the Toolkit disk which is or was offered from CATS. See the Introductory postings for the required info. Snoop will put descriptions of each AllocMem and FreeMem call (via SetFunction()'s) out the serial port at 9600 bps, so hook up a fast terminal and watch the world go by. And while you have the terminal hooked up, you can play with RomWACK, too. -- ------------------------------------------------------------------------- Andy Heffernan uunet!glyph!nichiren [1222 - 1282] ------------------------------------------------------------------------- "Dogpile on the rabbit! Dogpile on the rabbit!"
sjorr@rose.waterloo.edu (Stephen Orr) (07/30/89)
> .... The idea was to SetFunction() the >FreeMem() function to zero memory *as it is freed*. Then either a >SetFunction()ed AllocMem() function or some separate task/function >could check the free list to make sure it is zeroed. If not, it is a >sign of corruption. One thing to watch for, zeroing the memory chunk may not be enough, when the OS coallesces free chunks it probably leaves the old forward pointer alone, and this could appear as 'corrupted memory' unless the coallesce is done one the fly just 'expanding' an existing free memory chunk, I'm not sure, but this is to be considered. Stephen Orr { The opinions expressed herin ARE those of my employer... I'm self employed ! - Stephen Orr } >-- >Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu
usenet@cps3xx.UUCP (Usenet file owner) (07/31/89)
In article <SHADOW.89Jul29071247@pawl.rpi.edu> shadow@pawl.rpi.edu (Deven T. Corzine) writes: > >On 28 Jul 89 19:44:05 GMT, >fgd3@jc3b21.UUCP (Fabbian G. Dufoe) said: > >FGD> In article <7408@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) [ --stuff ] >FreeMem() function to zero memory *as it is freed*. Then either a >SetFunction()ed AllocMem() function or some separate task/function There is a sticky problem with this idea, function() { x=AllocMem() playwithit(x); Forbid(); FreeMem(x,len); playsomemore(x); Permit(); } No if you go zeroing things out at the FreeMem call, you can potentially screw somebody up. When a task is ending, does the Tasks memory get freed before it actually returns? REAL NAME: Joe Porkka jap@frith.cl.msu.edu
shadow@pawl.rpi.edu (Deven T. Corzine) (07/31/89)
On 30 Jul 89 17:48:54 GMT,
usenet@cps3xx.UUCP (Usenet file owner) said:
porkka> In article <SHADOW.89Jul29071247@pawl.rpi.edu>
porkka> shadow@pawl.rpi.edu (Deven T. Corzine) writes:
Deven> FreeMem() function to zero memory *as it is freed*. Then either a
Deven> SetFunction()ed AllocMem() function or some separate task/function
porkka> There is a sticky problem with this idea,
porkka> function() {
porkka> x=AllocMem()
porkka> playwithit(x);
porkka> Forbid();
porkka> FreeMem(x,len);
porkka> playsomemore(x);
porkka> Permit();
porkka> }
porkka> No if you go zeroing things out at the FreeMem call, you can
porkka> potentially screw somebody up.
True. Of course, the first 8 bytes of the freed chunk is going to be
lost to the next-node pointer anyhow. This difficulty could be
avoided by adding some state information. When FreeMem() is called,
if the task is in a forbidden (or disabled) state, free the memory,
but do not zero. Either simply set a "zero free memory" bit or add an
entry to a list of freed, yet-to-be-zeroed blocks of memory. (or have
a separate lists for free memory -- one zeroed, one not)
Using a single bit somewhere is simplest, but it means the entire free
list must be scanned and rezeroed, a likely-to-be-lengthy procedure,
and has the disadvantage of possibly missing any "unauthorized"
mucking about in free memory.
Using a list of to-be-zeroed free blocks solves that, but has the
problem of requiring a memory allocation to free memory, unless this
bit of state information could possibly be encoded (safely and
legally) in the existing data structures. Perhaps in a ln_Priority
field? Hmm.
This whole thing brings another point to mind -- writing ReallocMem()
to do FreeMem, AllocMem, CopyMem, and AllocAbs on failure would not
work, because of the node pointer placed in the first 8 bytes upon
that first FreeMem... [subtle flaw, that.]
porkka> When a task is ending, does the Tasks memory get freed before
porkka> it actually returns?
Any memory in the task's tc_MemEntry field will be safely freed
(regardless of any such zeroing) by RemTask(). (If a compiler's
exit() code frees the memory it is running in before calling RemTask
(or returning, normally to the system default finalizer code, which is
a RemTask(0L)) then there could indeed be problems.
Deven
--
Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu
Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750
Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven
Simple things should be simple and complex things should be possible.
shadow@pawl.rpi.edu (Deven T. Corzine) (07/31/89)
On 30 Jul 89 14:18:04 GMT, sjorr@rose.waterloo.edu (Stephen Orr) said: Deven> The idea was to SetFunction() the FreeMem() function to zero Deven> memory *as it is freed*. Then either a SetFunction()ed Deven> AllocMem() function or some separate task/function could check Deven> the free list to make sure it is zeroed. If not, it is a sign Deven> of corruption. Stephen> One thing to watch for, zeroing the memory chunk may Stephen> not be enough, when the OS coallesces free chunks it probably Stephen> leaves the old forward pointer alone, and this could appear Stephen> as 'corrupted memory' unless the coallesce is done one the Stephen> fly just 'expanding' an existing free memory chunk, I'm not Stephen> sure, but this is to be considered. Oh! Sharp thinking, there. Yes, I had neglected to consider to that extent. I believe the coalescing is done by the FreeMem call. Certainly it would need to be zeroed when combining adjacent free memory blocks. Deven -- Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750 Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven Simple things should be simple and complex things should be possible.
rosenber@ra.abo.fi (Robin Rosenberg INF) (07/31/89)
In article <3985@cps3xx.UUCP>, usenet@cps3xx.UUCP (Usenet file owner) writes: > Forbid(); > FreeMem(x,len); > playsomemore(x); > Permit(); > > No if you go zeroing things out at the FreeMem call, you can potentially > screw somebody up. FreeMem is just that. It gives the memory _back_ to the system which is then free to tag and use the memory anyway it wants to. Using free'd memory is a bad habit. Get rid of it. It does not work. Forbid() doesn't help. ----- Robin Rosenberg
lphillips@lpami.wimsey.bc.ca (Larry Phillips) (07/31/89)
In <SHADOW.89Jul31154235@pawl.rpi.edu>, shadow@pawl.rpi.edu (Deven T. Corzine) writes: >Robin> In article <3985@cps3xx.UUCP>, usenet@cps3xx.UUCP (Usenet file >Robin> owner) writes: >porkka> Forbid(); >porkka> FreeMem(x,len); >porkka> playsomemore(x); >porkka> Permit(); > >porkka> Now if you go zeroing things out at the FreeMem call, you can >porkka> potentially screw somebody up. > >Robin> FreeMem is just that. It gives the memory _back_ to the system >Robin> which is then free to tag and use the memory anyway it wants >Robin> to. Using free'd memory is a bad habit. Get rid of it. It does >Robin> not work. Forbid() doesn't help. > >And yet, it IS assumed to be safe, if you're forbidden. (It *may* >even be (semi) officially sanctioned. But I wouldn't count on it.) >Regardless, people *expect* this to work, and it would help not to >break it without needing to. Why would anyone assume this was a safe thing to do? If you want to wrap a Disable()/Enable() pair around that, I might be inclined to consider it semi-safe. -larry -- "So what the hell are we going to do with a Sun?" - Darlene Phillips - +-----------------------------------------------------------------------+ | // Larry Phillips | | \X/ lphillips@lpami.wimsey.bc.ca -or- uunet!van-bc!lpami!lphillips | | COMPUSERVE: 76703,4322 -or- 76703.4322@compuserve.com | +-----------------------------------------------------------------------+
utoddl@ecsvax.UUCP (Todd M. Lewis) (07/31/89)
In article <694@jc3b21.UUCP>, fgd3@jc3b21.UUCP (Fabbian G. Dufoe) writes: [...] > the next chunk pointer to be sure that was zero. However, any application > might allocate the memory, write on it, and free it without clearing it. > So finding non-zero values in free memory wouldn't mean you had discovered > a program which was writing on memory it didn't own. Right. Which is why I plan to SetFunction() FreeMem(), so that I can zero out any ram that is returned to the free memory pool. Thanks. Any other ideas? It looks like I'll have a little time to think about it before I get down to code--My wife is using the Amiga all the time now for her school work. Guess I'll have to buy another Amiga! > > --Fabbian Dufoe > 350 Ling-A-Mor Terrace South > St. Petersburg, Florida 33705 > 813-823-2350 > > UUCP: ...uunet!pdn!jc3b21!fgd3 Todd M. Lewis, utoddl@ecsvax.uncecs.edu, utoddl@ecsvax.bitnet To quote Eugene H. Spafford, "Crisis and Aftermath", Communications of the ACM, vol. 32, no. 6, (June 1989), p. 684: "It is curious that this many people [...] would assume to know the intent of the author based on the observed behavior of the program."
shadow@pawl.rpi.edu (Deven T. Corzine) (08/01/89)
On 30 Jul 89 21:27:01 GMT,
rosenber@ra.abo.fi (Robin Rosenberg INF) said:
Robin> In article <3985@cps3xx.UUCP>, usenet@cps3xx.UUCP (Usenet file
Robin> owner) writes:
porkka> Forbid();
porkka> FreeMem(x,len);
porkka> playsomemore(x);
porkka> Permit();
porkka> Now if you go zeroing things out at the FreeMem call, you can
porkka> potentially screw somebody up.
Robin> FreeMem is just that. It gives the memory _back_ to the system
Robin> which is then free to tag and use the memory anyway it wants
Robin> to. Using free'd memory is a bad habit. Get rid of it. It does
Robin> not work. Forbid() doesn't help.
And yet, it IS assumed to be safe, if you're forbidden. (It *may*
even be (semi) officially sanctioned. But I wouldn't count on it.)
Regardless, people *expect* this to work, and it would help not to
break it without needing to.
Deven
--
Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu
Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750
Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven
Simple things should be simple and complex things should be possible.
utoddl@ecsvax.UUCP (Todd M. Lewis) (08/02/89)
In article <SHADOW.89Jul31154235@pawl.rpi.edu}, shadow@pawl.rpi.edu (Deven T. Corzine) writes: } } On 30 Jul 89 21:27:01 GMT, } rosenber@ra.abo.fi (Robin Rosenberg INF) said: } } Robin> In article <3985@cps3xx.UUCP>, usenet@cps3xx.UUCP (Usenet file } Robin> owner) writes: } porkka> Forbid(); } porkka> FreeMem(x,len); } porkka> playsomemore(x); } porkka> Permit(); } } porkka> Now if you go zeroing things out at the FreeMem call, you can } porkka> potentially screw somebody up. } } Robin> FreeMem is just that. It gives the memory _back_ to the system } Robin> which is then free to tag and use the memory anyway it wants } Robin> to. Using free'd memory is a bad habit. Get rid of it. It does } Robin> not work. Forbid() doesn't help. } } And yet, it IS assumed to be safe, if you're forbidden. (It *may* } even be (semi) officially sanctioned. But I wouldn't count on it.) } Regardless, people *expect* this to work, and it would help not to } break it without needing to. I don't assume it is safe. In fact I will gladly take steps to detect such usage and purge that software from my disks. What do you think will happen to the above program when an MMU is running? No, if you want to mung around with memory, don't free it first. -- Todd
shadow@pawl.rpi.edu (Deven T. Corzine) (08/02/89)
On 1 Aug 89 18:48:04 GMT, utoddl@ecsvax.UUCP (Todd M. Lewis) said: Todd> I don't assume it is safe. In fact I will gladly take steps to Todd> detect such usage and purge that software from my disks. What Todd> do you think will happen to the above program when an MMU is Todd> running? No, if you want to mung around with memory, don't free Todd> it first. It's certainly best not to assume it's safe, but I wouldn't want to break programs unnecessarily. (as a general rule) Deven -- Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750 Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven Simple things should be simple and complex things should be possible.
walker@sas.UUCP (Doug Walker) (08/02/89)
In article <7408@ecsvax.UUCP> utoddl@ecsvax.UUCP (Todd M. Lewis) writes: > >I have a program which seems to use memory it doesn't own >from time to time, and I'd like some way to catch it in the act. >I've been thinking about a method to see if/when it messes up free >memory, and I'd like to know why this wouldn't work: I, too, have been interested in this problem, and I have a link library of memory debugging routines that does the following: * Trash all allocated memory before the application sees it * Instead of actually freeing memory, put it on a chain until the machine runs out of memory. Trash it. Check it to make sure it stays trashed. (Don't trash with 0's, though - too often that's what the transgressor wants). * Allocate a header and trailer before/after all allocations and set it to a known value. Check the headers/trailers every so often; when they change, somebody trashed memory (usually a buffer overwrite). * Detect the application not freeing memory upon exit * Detect an invalid FreeMem or free() call * Export this knowledge to an external process (MemWatch III) that could perform these checks for you asynchronously * Any time an error occurs, give the filename and line number that allocated the memory Any other suggestions for features are welcome, including what to do if the external process detects an error. Right now I am putting up an alert like MemWatch II did; Andy Finkel requested that I put debugging info out the serial port instead. Right now I have the link library working and the external process working with alerts. --Doug
shadow@pawl.rpi.edu (Deven T. Corzine) (08/03/89)
On 30 Jul 89 22:15:49 GMT,
lphillips@lpami.wimsey.bc.ca (Larry Phillips) said:
In <SHADOW.89Jul31154235@pawl.rpi.edu>, shadow@pawl.rpi.edu
(Deven T. Corzine) writes:
Deven> And yet, it IS assumed to be safe, if you're forbidden. (It
Deven> *may* even be (semi) officially sanctioned. But I wouldn't
Deven> count on it.) Regardless, people *expect* this to work, and it
Deven> would help not to break it without needing to.
Larry> Why would anyone assume this was a safe thing to do? If you
Larry> want to wrap a Disable()/Enable() pair around that, I might be
Larry> inclined to consider it semi-safe.
Ah, but interrupts are forbidden to call the memory allocatio
routines, as the system free memory list may well be corrupt. So,
people expect Forbid()/Permit() to work...
Deven
--
Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu
Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750
Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven
Simple things should be simple and complex things should be possible.
utoddl@ecsvax.UUCP (Todd M. Lewis) (08/03/89)
In article <SHADOW.89Aug2140114@pawl.rpi.edu>, shadow@pawl.rpi.edu (Deven T. Corzine) writes: > Ah, but interrupts are forbidden to call the memory allocatio > routines, as the system free memory list may well be corrupt. So, > people expect Forbid()/Permit() to work... People also expect FreeMem() to return the memory to the system. FreeMem() can be expected to change the contents of the freed ram (for example, to link it into the free memory list). Think of it as #define TrashThisRAM FreeMem and you'll begin to get my point. -- Todd
shadow@pawl.rpi.edu (Deven T. Corzine) (08/06/89)
In article <SHADOW.89Aug2140114@pawl.rpi.edu>, shadow@pawl.rpi.edu (Deven T. Corzine) writes: Deven> Ah, but interrupts are forbidden to call the memory allocatio Deven> routines, as the system free memory list may well be corrupt. Deven> So, people expect Forbid()/Permit() to work... On 3 Aug 89 14:34:48 GMT, utoddl@ecsvax.UUCP (Todd M. Lewis) said: Todd> People also expect FreeMem() to return the memory to the system. Todd> FreeMem() can be expected to change the contents of the freed Todd> ram (for example, to link it into the free memory list). Think Todd> of it as Todd> #define TrashThisRAM FreeMem Todd> and you'll begin to get my point. I understand your point completely. My point is that people have used recently freed memory while forbidden, and know it to currently work. They' can't reasonably *expect* it to work, but to break it unnecessarily isn't of value either. Deven -- Deven T. Corzine Internet: deven@rpi.edu, shadow@pawl.rpi.edu Snail: 2214 12th Street, Troy, NY 12180 Phone: (518) 271-0750 Bitnet: deven@rpitsmts, userfxb6@rpitsmts UUCP: uunet!rpi!deven Simple things should be simple and complex things should be possible.