Partial string loss with sprintf/strcat

Discussion in 'C Programming' started by Neal Barney, Jul 22, 2006.

  1. Neal Barney

    Neal Barney Guest

    I have a C program which runs on a device using a Zilog Z180
    microprocessor. While it can address 1MB of RAM, it can only address
    64KB at any given time. And of that only 16KB can be used for "stack
    and heap space". So I'm running in a very memory constricted
    environment.

    The program "speaks" a proprietary protocol which sends ASCII strings
    back and forth from the device to the server. Within the past couple of
    months we've been noticing errors in our server logs and people have
    been complaing of failures. Upon viewing server logs we've noticed the
    string the device is sending is missing it's header.

    The string gets built like so:

    /* Global variable */
    char Message[512];

    /* Local variable */
    char tmpMsg[512];

    /* var1 - var4 (local variables) get populated with values... */

    /* Build the header */
    sprintf( tmpMsg, "|XX|%s|0|00.00|XXXX:XXXX|XXXX:%s|XXXX:%s|XXXX:%s",
    var1, var2, var3, var4 );

    /* X's are alphas and zeros are numbers */

    if ( conditionTrue )
    strcat( tmpMsg, ",S" );
    else
    strcat( tmpMsg, ",H" );

    /* Continue building the rest of the string... */

    /* Put msg length at beginning of string*/
    len = strlen( tmpMsg );
    sprintf( Message, "%04d%s", len, tmpMsg );

    return;


    Now when the device transmits the string to the server, the server logs
    something similar to:

    0089,S|XXXX:XXXX|XXXX:XXXX|... (and so on)
    or sometimes something like
    0093 ,S|XXXX:XXXX|XXXX:XXXX|... (and so on)

    The entire header appears to be getting lost. The calculated message
    length matches the size of the string without the header, which may
    suggest:

    a) The sprintf that places the header in tmpMsg is failing (but why?).
    b) A bug in strcat for the C library I'm using is wiping out the
    string/starting at the beginning of the string. (It doesn't do it later
    in the same function or elsewhere in the program).
    c) "Something else" is walking on that part of memory.
    d) ...?

    This is not 100% reproduceable (in fact, in the past 9 months I have
    only personally seen it happen once on my test device, and I can't
    reproduce it there). But we are seeing this happen in production
    multiple times a day from different devices. Unplugging the device and
    plugging it back in will often "fix" the problem (though it may occur
    again in the future).

    What are the most likely causes for this problem? Solutions?
     
    Neal Barney, Jul 22, 2006
    #1
    1. Advertising

  2. On 2006-07-22, Neal Barney <> wrote:
    > The program "speaks" a proprietary protocol which sends ASCII strings
    > back and forth from the device to the server. Within the past couple of
    > months we've been noticing errors in our server logs and people have
    > been complaing of failures. Upon viewing server logs we've noticed the
    > string the device is sending is missing it's header.
    >
    > The string gets built like so:
    >
    > /* Global variable */
    > char Message[512];
    >
    > /* Local variable */
    > char tmpMsg[512];
    >
    > /* var1 - var4 (local variables) get populated with values... */
    >
    > /* Build the header */
    > sprintf( tmpMsg, "|XX|%s|0|00.00|XXXX:XXXX|XXXX:%s|XXXX:%s|XXXX:%s",
    > var1, var2, var3, var4 );
    >


    Are you sure that var1 - var4 take up no more than 465 bytes?

    > /* X's are alphas and zeros are numbers */
    >
    > if ( conditionTrue )
    > strcat( tmpMsg, ",S" );
    > else
    > strcat( tmpMsg, ",H" );
    >


    Looks like I meant 463 bytes...

    > /* Continue building the rest of the string... */


    ....or less. Are you sure that a 512-byte buffer is enough? If not, you
    need to have some way of dealing with running out of space. Perhaps you
    could eliminate 4-digit dates (they aren't too important in human-read
    logs), or detailed timestamp information. It really depends on what you
    need from the log files, and what your design is.

    If you overflowed your buffer in the above code, you've left the realm
    of C, and have entered Honah Lee.

    > Now when the device transmits the string to the server, the server logs
    > something similar to:
    >
    > 0089,S|XXXX:XXXX|XXXX:XXXX|... (and so on)
    > or sometimes something like
    > 0093 ,S|XXXX:XXXX|XXXX:XXXX|... (and so on)
    >
    > The entire header appears to be getting lost.


    Which part is the header? What exactly should be displayed that isn't?

    > This is not 100% reproduceable (in fact, in the past 9 months I have
    > only personally seen it happen once on my test device, and I can't
    > reproduce it there). But we are seeing this happen in production
    > multiple times a day from different devices. Unplugging the device and
    > plugging it back in will often "fix" the problem (though it may occur
    > again in the future).
    >
    > What are the most likely causes for this problem? Solutions?


    Sure sounds like a buffer overflow, or some other undefined memory issue.

    --
    Andrew Poelstra <website down>
    My server is down; you can't mail
    me, nor can I post convieniently.
     
    Andrew Poelstra, Jul 22, 2006
    #2
    1. Advertising

  3. Neal Barney

    Eric Sosman Guest

    Neal Barney wrote:
    > I have a C program which runs on a device using a Zilog Z180
    > microprocessor. While it can address 1MB of RAM, it can only address
    > 64KB at any given time. And of that only 16KB can be used for "stack
    > and heap space". So I'm running in a very memory constricted
    > environment.
    >
    > The program "speaks" a proprietary protocol which sends ASCII strings
    > back and forth from the device to the server. Within the past couple of
    > months we've been noticing errors in our server logs and people have
    > been complaing of failures. Upon viewing server logs we've noticed the
    > string the device is sending is missing it's header.
    >
    > The string gets built like so:
    >
    > /* Global variable */
    > char Message[512];
    >
    > /* Local variable */
    > char tmpMsg[512];
    >
    > /* var1 - var4 (local variables) get populated with values... */
    >
    > /* Build the header */
    > sprintf( tmpMsg, "|XX|%s|0|00.00|XXXX:XXXX|XXXX:%s|XXXX:%s|XXXX:%s",
    > var1, var2, var3, var4 );
    >
    > /* X's are alphas and zeros are numbers */
    >
    > if ( conditionTrue )
    > strcat( tmpMsg, ",S" );
    > else
    > strcat( tmpMsg, ",H" );
    >
    > /* Continue building the rest of the string... */
    >
    > /* Put msg length at beginning of string*/
    > len = strlen( tmpMsg );
    > sprintf( Message, "%04d%s", len, tmpMsg );
    >
    > return;
    >
    >
    > Now when the device transmits the string to the server, the server logs
    > something similar to:
    >
    > 0089,S|XXXX:XXXX|XXXX:XXXX|... (and so on)
    > or sometimes something like
    > 0093 ,S|XXXX:XXXX|XXXX:XXXX|... (and so on)


    Like Andrew Poelstra, I was at first suspicious of a buffer
    overflow. But the lengths shown in these fragments don't support
    that hypothesis, not directly at any rate.

    I think you need to show us actual code, not paraphrases.
    The bug might lie in "Continue building the rest of the string"
    or even in the way Message and tmpMsg are declared (what you've
    shown is obviously not the full story; it doesn't, for example,
    show how one of them gets to be "global" and the other "local").

    If you don't want to show your (proprietary) code and would
    prefer to debug on your own, I'd suggest adding a few printf()
    calls to output interesting values. For example, the value
    returned by sprintf() might be interesting, especially if it
    is markedly different from the value returned by strlen(). I'm
    thinking of something along the lines of

    tmpLen = sprintf(tmpMsg, ...);
    if (tmplen != strlen(tmpMsg))
    print_everything_you_can_and_then_die();

    if (conditionTrue)
    strcat(tmpMsg, ",S");
    else
    strcat(tmpMsg, ",H");
    tmpLen += 2;
    if (tmplen != strlen(tmpMsg))
    print_everything_you_can_and_then_die();

    /* Continue building the rest of the string... */

    len = strlen( tmpMsg );
    sprintf( Message, "%04d%s", len, tmpMsg );
    if (len + 4 != strlen(Message))
    print_everything_you_can_and_die();

    --
    Eric Sosman
    lid
     
    Eric Sosman, Jul 22, 2006
    #3
  4. Neal Barney

    jacob navia Guest

    Eric Sosman a écrit :
    > If you don't want to show your (proprietary) code and would
    > prefer to debug on your own, I'd suggest adding a few printf()
    > calls to output interesting values. For example, the value
    > returned by sprintf() might be interesting, especially if it
    > is markedly different from the value returned by strlen(). I'm
    > thinking of something along the lines of
    >
    > tmpLen = sprintf(tmpMsg, ...);
    > if (tmplen != strlen(tmpMsg))
    > print_everything_you_can_and_then_die();
    >


    This is a very good proposition. It means to try to verify
    what each part of the software is doing. The result of the
    sprintf call is a good starting point to start adding redundant
    verifications, (also called sanity checks!!!) into the software.

    When something like this happens, MANY things can be the reason
    for the observed symptoms: run time data corruption, buffer
    overflows, etc.

    Starting with the result of the sprintf call
    you can verify also that it is less than the length of the
    buffer!!!
     
    jacob navia, Jul 22, 2006
    #4
  5. Neal Barney

    Neal Barney Guest

    Eric Sosman wrote:
    > Like Andrew Poelstra, I was at first suspicious of a buffer
    > overflow. But the lengths shown in these fragments don't support
    > that hypothesis, not directly at any rate.
    >
    > I think you need to show us actual code, not paraphrases.
    > The bug might lie in "Continue building the rest of the string"
    > or even in the way Message and tmpMsg are declared (what you've
    > shown is obviously not the full story; it doesn't, for example,
    > show how one of them gets to be "global" and the other "local").
    >
    > If you don't want to show your (proprietary) code and would
    > prefer to debug on your own, I'd suggest adding a few printf()
    > calls to output interesting values. For example, the value
    > returned by sprintf() might be interesting, especially if it
    > is markedly different from the value returned by strlen(). I'm
    > thinking of something along the lines of
    >
    > tmpLen = sprintf(tmpMsg, ...);
    > if (tmplen != strlen(tmpMsg))
    > print_everything_you_can_and_then_die();
    >
    > if (conditionTrue)
    > strcat(tmpMsg, ",S");
    > else
    > strcat(tmpMsg, ",H");
    > tmpLen += 2;
    > if (tmplen != strlen(tmpMsg))
    > print_everything_you_can_and_then_die();
    >
    > /* Continue building the rest of the string... */
    >
    > len = strlen( tmpMsg );
    > sprintf( Message, "%04d%s", len, tmpMsg );
    > if (len + 4 != strlen(Message))
    > print_everything_you_can_and_die();
    >


    My first suspicion was also a buffer overflow. However, the header
    should always be between 78 and 85 characters. The entire message
    rarely exceeds 200 bytes so a 512 byte buffer -should- be safe, but an
    overflow is still possible. After trolling through the code for nearly
    a week, I have yet to find where it is overflowing if that is what is
    happening.

    I understand that it's hard to know what advice to give with the
    mocked-up code I presented. I would post the actual code, but it's not
    -my- code, it's that of my employer and I'm sure they wouldn't
    appreciate it. :)

    I do like your suggestion of testing the string length at every step of
    the code and dying if it doesn't meet the lengths that I expect. The
    only issue that still leaves for me is that I am unable to reproduce it
    myself on my development box. This code works about 95-98% of the time
    for those using it production (though some people seem to get it more
    than their fair share). I guess if it did die on them in production,
    I'd have a better idea of where the problem was.

    Thank you for everyone who has responded so far. If anyone else has any
    ideas or suggestions, I'm still open to them.
     
    Neal Barney, Jul 23, 2006
    #5
  6. Neal Barney

    Flash Gordon Guest

    Neal Barney wrote:

    <snip suspicion of buffer overflow>

    > I do like your suggestion of testing the string length at every step of
    > the code and dying if it doesn't meet the lengths that I expect. The
    > only issue that still leaves for me is that I am unable to reproduce it
    > myself on my development box. This code works about 95-98% of the time
    > for those using it production (though some people seem to get it more
    > than their fair share). I guess if it did die on them in production,
    > I'd have a better idea of where the problem was.
    >
    > Thank you for everyone who has responded so far. If anyone else has any
    > ideas or suggestions, I'm still open to them.


    Could you get it to log data that would overflow the buffer somewhere
    you (or a customer) could retrieve the log then take some appropriate
    recovery action?
    --
    Flash Gordon, living in interesting times.
    Web site - http://home.flash-gordon.me.uk/
    comp.lang.c posting guidelines and intro:
    http://clc-wiki.net/wiki/Intro_to_clc
     
    Flash Gordon, Jul 23, 2006
    #6
  7. Neal Barney

    Eric Sosman Guest

    Neal Barney wrote:
    > [...]
    > I do like your suggestion of testing the string length at every step of
    > the code and dying if it doesn't meet the lengths that I expect. The
    > only issue that still leaves for me is that I am unable to reproduce it
    > myself on my development box. This code works about 95-98% of the time
    > for those using it production (though some people seem to get it more
    > than their fair share). I guess if it did die on them in production,
    > I'd have a better idea of where the problem was.


    The important thing isn't really that the program die,
    but that it die noisily, having logged everything that might
    conceivably be of value in diagnosing the problem. When a
    bug is hard to reproduce, it is particularly important to
    capture as much information as possible.

    One way to do this, of course, is to output everything
    you can every time you go through the suspect code. That has
    an unfortunate tendency to drown you with too much data, so a
    technique that's fairly frequently employed is to log everything
    to a circular buffer, going merrily 'round and 'round as long
    as everything looks all right. Then when something screwy is
    detected, you dump the buffer containing (by now) the information
    collected from the last dozen or hundred or whatever log points.

    Having captured the bad trace, it isn't even essential that
    the program actually die -- but from your description, it seems
    that the bogus transmission may have disrupted communication
    with the other end of the wire, and the program might have some
    difficulty continuing "normal operations" anyhow.

    --
    Eric Sosman
    lid
     
    Eric Sosman, Jul 23, 2006
    #7
  8. Neal Barney

    Ivanna Pee Guest

    Neal Barney wrote:
    > I have a C program which runs on a device using a Zilog Z180
    > microprocessor. While it can address 1MB of RAM, it can only address
    > 64KB at any given time. And of that only 16KB can be used for "stack
    > and heap space". So I'm running in a very memory constricted
    > environment.
    >
    > The program "speaks" a proprietary protocol which sends ASCII strings
    > back and forth from the device to the server. Within the past couple of
    > months we've been noticing errors in our server logs and people have
    > been complaing of failures. Upon viewing server logs we've noticed the
    > string the device is sending is missing it's header.
    >
    > The string gets built like so:
    >
    > /* Global variable */
    > char Message[512];
    >
    > /* Local variable */
    > char tmpMsg[512];
    >
    > /* var1 - var4 (local variables) get populated with values... */
    >
    > /* Build the header */
    > sprintf( tmpMsg, "|XX|%s|0|00.00|XXXX:XXXX|XXXX:%s|XXXX:%s|XXXX:%s",
    > var1, var2, var3, var4 );
    >
    > /* X's are alphas and zeros are numbers */
    >
    > if ( conditionTrue )
    > strcat( tmpMsg, ",S" );
    > else
    > strcat( tmpMsg, ",H" );
    >
    > /* Continue building the rest of the string... */
    >
    > /* Put msg length at beginning of string*/
    > len = strlen( tmpMsg );
    > sprintf( Message, "%04d%s", len, tmpMsg );
    >
    > return;
    >
    >
    > Now when the device transmits the string to the server, the server logs
    > something similar to:
    >
    > 0089,S|XXXX:XXXX|XXXX:XXXX|... (and so on)
    > or sometimes something like
    > 0093 ,S|XXXX:XXXX|XXXX:XXXX|... (and so on)
    >
    > The entire header appears to be getting lost. The calculated message
    > length matches the size of the string without the header, which may
    > suggest:
    >
    > a) The sprintf that places the header in tmpMsg is failing (but why?).
    > b) A bug in strcat for the C library I'm using is wiping out the
    > string/starting at the beginning of the string. (It doesn't do it later
    > in the same function or elsewhere in the program).
    > c) "Something else" is walking on that part of memory.
    > d) ...?
    >
    > This is not 100% reproduceable (in fact, in the past 9 months I have
    > only personally seen it happen once on my test device, and I can't
    > reproduce it there). But we are seeing this happen in production
    > multiple times a day from different devices. Unplugging the device and
    > plugging it back in will often "fix" the problem (though it may occur
    > again in the future).
    >
    > What are the most likely causes for this problem? Solutions?


    Does the transmitting program crash when this occurs? It doesn't sound
    like it if the problem data packet is recieved at the other end.

    I would guess that during "Continue building the rest of the string"
    you are stomping on tmpMsg[0], perhaps overrunning the local data on
    the stack just below it.

    How often does this thing transmit? Speed up the xmission interval on
    the test setup to hopefully increase the frequency at which it fails,
    and take a look at the stack when it does.
     
    Ivanna Pee, Jul 24, 2006
    #8
  9. On Sat, 22 Jul 2006 20:12:51 +0000 (UTC), Neal Barney
    <> wrote:

    > I have a C program which runs on a device using a Zilog Z180
    > microprocessor. While it can address 1MB of RAM, it can only address
    > 64KB at any given time. And of that only 16KB can be used for "stack
    > and heap space". So I'm running in a very memory constricted
    > environment.
    >
    > The program "speaks" a proprietary protocol which sends ASCII strings
    > back and forth from the device to the server. Within the past couple of
    > months we've been noticing errors in our server logs and people have
    > been complaing of failures. Upon viewing server logs we've noticed the
    > string the device is sending is missing it's header.
    >
    > The string gets built like so:
    >
    > /* Global variable */
    > char Message[512];
    >
    > /* Local variable */
    > char tmpMsg[512];


    > /* Build the header */
    > sprintf( tmpMsg, <snip>
    > /* Continue building the rest of the string... */
    >
    > /* Put msg length at beginning of string*/
    > len = strlen( tmpMsg );
    > sprintf( Message, "%04d%s", len, tmpMsg );
    >
    > return;
    >
    >
    > Now when the device transmits the string to the server, <snip>
    > The entire header appears to be getting lost. The calculated message
    > length matches the size of the string without the header, which may
    > suggest:
    >
    > a) The sprintf that places the header in tmpMsg is failing (but why?).
    > b) A bug in strcat for the C library I'm using is wiping out the
    > string/starting at the beginning of the string. (It doesn't do it later
    > in the same function or elsewhere in the program).
    > c) "Something else" is walking on that part of memory.
    > d) ...?
    >
    > This is not [very] reproduceable <snip>


    Like other responders I don't see any likely culprits in the partial
    code you posted. As already suggested collecting information about the
    state and sequence(s) leading up to a fault is likely to be helpful in
    narrowing down the suspects, and/or helping you construct cases that
    will reproduce it in test. I realize general advice like this is not
    much more helpful than telling you that candy tastes good.

    It may be obvious, but if you haven't done so already I would inspect
    the generated (and I assume shipped) assembler/machine code for your
    routine and if reasonably possible (and not prohibited) for the
    library routines your code calls (sprintf, strcat, etc.) just to make
    sure the compiler/implementor isn't doing something dumb somewhere.
    (But that part would be offtopic for c.l.c, except maybe for questions
    about exactly what standard-library routines are required to do,
    especially in corner cases, which I don't see you approaching.)

    I do however see something I would do differently in a memory
    constrained environment (and maybe even a normal one) which might or
    might not be of help to you. Instead of building the message body in
    one buffer (tmpMsg) and then copying it plus a small fixed-length
    prefix into another (Message), do something like:
    size_t used = sprintf (&Message[4], "format", headerargs);
    strcpy (&Message[4+used], nextitem); used += nextitemlen;
    etc., bumping used in the process as long as not too difficult
    char tmpLen [4+1];
    sprintf (tmpLen, "%04d", used /* or strlen (Message+4) */ );
    memcpy (&Message[0], tmpLen, 4);
    /* you can simplify &Message[n] to Message+n and particularly
    &Message[0] to Message if your compiler is so badly lame it doesn't */

    If the code generated, by your compiler for your target*, to access
    (within) the global/static buffer is less efficient, use a pointer
    into it instead:
    /* register? */ char * putMsg = &Message[4];
    putMsg += sprintf (putMsg, "format", headerargs); etc.
    (* For 8080 and IIRC Z80, linker-resolved globals or statics are
    actually better, but I don't know if Z180 is different here.)

    Also, if you have snprintf (or _snprintf or somesuch) available in
    your library, you might use that instead. (It is standard in C99 for
    hosted environments, but I'd guess the implementation you're using
    isn't even trying to conform at that level.) For the code and data you
    posted it doesn't look likely that you're overflowing, but it could be
    arbitrarily bad if you do, so if you can be certain at reasonable cost
    you don't it's probably worth it just to rule that out.

    - David.Thompson1 at worldnet.att.net
     
    Dave Thompson, Jul 31, 2006
    #9
  10. Neal Barney

    Neal Barney Guest

    On Mon, 31 Jul 2006 05:11:18 +0000, Dave Thompson wrote:
    > Like other responders I don't see any likely culprits in the partial
    > code you posted. As already suggested collecting information about the
    > state and sequence(s) leading up to a fault is likely to be helpful in
    > narrowing down the suspects, and/or helping you construct cases that
    > will reproduce it in test. I realize general advice like this is not
    > much more helpful than telling you that candy tastes good.


    Well, I suppose it is a small comfort to me that I'm not crazy and didn't
    overlook something patently obvious. After further research I do believe
    that I have a better idea of what is going on. The stack and heap size
    must be pre-declared and it appears as if the declared stack size is too
    small. The stack space is being exceeded (or so it appears). I have
    given the stack an additional 1000 bytes and will do further testing to
    see if this resolves the issue. If it doesn't I'm in trouble. :) With
    that change to the stack size that puts my memory usage at 16302 bytes out
    of 16384. Talk about pushing the limits... :-O

    > It may be obvious, but if you haven't done so already I would inspect
    > the generated (and I assume shipped) assembler/machine code for your
    > routine and if reasonably possible (and not prohibited) for the library
    > routines your code calls (sprintf, strcat, etc.) just to make sure the
    > compiler/implementor isn't doing something dumb somewhere. (But that
    > part would be offtopic for c.l.c, except maybe for questions about
    > exactly what standard-library routines are required to do, especially in
    > corner cases, which I don't see you approaching.)


    A good suggestion but it's been years since I inspected assembler level
    code. And this machine uses a modified variant of the "Amsterdam
    compiler" that compiles to EM byte-code. The machine runs an interpreter
    that translates the EM code to native machine calls. I suppose there may
    be enough information out there to learn more about the EM byte code and
    figure things out, but that will probably be a last resort.

    > I do however see something I would do differently in a memory
    > constrained environment (and maybe even a normal one) which might or
    > might not be of help to you. Instead of building the message body in one
    > buffer (tmpMsg) and then copying it plus a small fixed-length prefix
    > into another (Message), do something like:
    > size_t used = sprintf (&Message[4], "format", headerargs); strcpy
    > (&Message[4+used], nextitem); used += nextitemlen; etc., bumping used
    > in the process as long as not too difficult char tmpLen [4+1]; sprintf
    > (tmpLen, "%04d", used /* or strlen (Message+4) */ ); memcpy
    > (&Message[0], tmpLen, 4);
    > /* you can simplify &Message[n] to Message+n and particularly
    > &Message[0] to Message if your compiler is so badly lame it doesn't */


    I already rewrote the function to do exactly what you are suggesting
    (though implemented a little differently). It should now be more
    efficient as well as likely being easier to see which call is causing me
    the issue. We'll see if this makes a difference.

    > If the code generated, by your compiler for your target*, to access
    > (within) the global/static buffer is less efficient, use a pointer into
    > it instead:
    > /* register? */ char * putMsg = &Message[4]; putMsg += sprintf
    > (putMsg, "format", headerargs); etc.
    > (* For 8080 and IIRC Z80, linker-resolved globals or statics are
    > actually better, but I don't know if Z180 is different here.)
    >
    > Also, if you have snprintf (or _snprintf or somesuch) available in your
    > library, you might use that instead. (It is standard in C99 for hosted
    > environments, but I'd guess the implementation you're using isn't even
    > trying to conform at that level.) For the code and data you posted it
    > doesn't look likely that you're overflowing, but it could be arbitrarily
    > bad if you do, so if you can be certain at reasonable cost you don't
    > it's probably worth it just to rule that out.


    I do not have a snprintf of any sort available to me. The compiler I'm
    using is ANSI C89 conforming, but that's as far as it goes (The compiler
    was written in 1993). I thought about using a third party library (such
    as trio), but in the end I decided that it wasn't worth linking against
    any additional code. Size is too important when the program gets
    downloaded at 2400/1200baud.

    I appreciate all of the replies I have received. I apologize for asking
    the question and then more-or-less disappearing. I was out of town on
    training and didn't have Usenet access. But all of the suggestions have
    been much appreciated.
     
    Neal Barney, Aug 3, 2006
    #10
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Billy
    Replies:
    2
    Views:
    528
    Billy
    Feb 1, 2006
  2. Thomas Heller
    Replies:
    13
    Views:
    894
    Michele Simionato
    Feb 8, 2007
  3. J. Clifford Dyer

    Re: Partial 1.0 - Partial classes for Python

    J. Clifford Dyer, Feb 8, 2007, in forum: Python
    Replies:
    0
    Views:
    539
    J. Clifford Dyer
    Feb 8, 2007
  4. Replies:
    24
    Views:
    1,232
    Greg Comeau
    Oct 30, 2005
  5. Replies:
    12
    Views:
    1,326
Loading...

Share This Page