catfood@NCoast.ORG (Mark W. Schumann) (10/27/90)
DLV101@psuvm.psu.edu (Dwaine VanBibber) writes: >Does anyone have some general rules for porting C programs developed under >UNIX to MS-DOS? I've developed a small language interpreter that I would >like to be able to run under DOS. I have already attempted this, and it >compiles fine using TC++, however I keep getting stack overflow. I am >using the large memory model. Setting TC++'s _stklen variable to something >large doesn't seem to help any either. I should also point out that the >program contains a recursive descent parser and function calls are often >nested quite deep. Compiling with TC 2.01 yields the same results. Do I >have to do a complete rewrite (ie. eliminate deep function calls or resort >to bottom-up parsing), or can it be coerced to work? Any comments or >volunteers to examine code? Thanks in advance. >--Dwaine You have a problem with the Intel '86 architecture. No matter what you do in C on an '86 machine, the stack segment cannot exceed 64K. That's because the SS register is (wierd exceptions aside) held constant through execution of a program. Since an Intel offset is a 16-bit pointer, you are limited to the 64K. Sigh...I think you may have to rewrite. Sorry. -- ============================================================ Mark W. Schumann 3111 Mapledale Avenue, Cleveland 44109 USA Domain: catfood@ncoast.org UseNet: ...usenet.ins.cwru.edu!ncoast!catfood ============================================================
dfoster@jarthur.Claremont.EDU (Derek R. Foster) (10/31/90)
In article <1990Oct26.223541.26634@NCoast.ORG> catfood@NCoast.ORG (Mark W. Schumann) writes: >DLV101@psuvm.psu.edu (Dwaine VanBibber) writes: >>Does anyone have some general rules for porting C programs developed under >>UNIX to MS-DOS? I've developed a small language interpreter that I would >>like to be able to run under DOS. I have already attempted this, and it >>compiles fine using TC++, however I keep getting stack overflow. I am >>using the large memory model. Setting TC++'s _stklen variable to something >>large doesn't seem to help any either. I should also point out that the >>program contains a recursive descent parser and function calls are often >>nested quite deep. Compiling with TC 2.01 yields the same results. Do I >>have to do a complete rewrite (ie. eliminate deep function calls or resort >>to bottom-up parsing), or can it be coerced to work? Any comments or >>volunteers to examine code? Thanks in advance. >>--Dwaine > >You have a problem with the Intel '86 architecture. No matter what you >do in C on an '86 machine, the stack segment cannot exceed 64K. That's >because the SS register is (wierd exceptions aside) held constant through >execution of a program. Since an Intel offset is a 16-bit pointer, you >are limited to the 64K. Although this is generally true in "nice" programs, there may be a somewhat kludgy workaround for this. DISCLAIMER: I haven't tried the following suggestion. This is all theory on my part, and I would be interested in hearing comments on it. Also the code I present is sketchy at best, and is probably outright wrong in some respects. Don't expect it to work; just use it as a model. Anyway, you might try changing the stack segment pointer to point into a new segment of your own when the old one gets filled. For instance, if you can identify a particular part of the program which will be called only when the stack is almost filled, you might try something like ... void huge char ABuffer[(some fancy calculation probably involving _stklen to preserve the ability to detect stack overflow) + 16 (the 16 is to ensure that I can always get a buffer of at least the size I want which starts on an even paragraph boundary)]; /* note that ABuffer is not meant to be a huge pointer to an array of chars, but a pointer to an array of 'huge chars'. This means it will be allocated its own data segment. I *THINK* this is the right syntax...*/ void StackSwapFunction(void) { unsigned oldSS; oldSS = _SS; _SS = FP_SEG(ABuffer+sizeof(ABuffer)); Do other function calls here.... possibly if they get too deep you could even do this same strategy with another buffer. Might even consider dynamic allocation of such buffers. Just remember that the stack segment has to start on an address that's a multiple of 16... _SS = oldSS; } All functions called by this function will put their return addresses, local variables, etc. in the new stack space, which is in the buffer. You may have other housekeeping tasks to do as well, possibly involving changing some global variables that are used internally by TC to detect end-of-stack. (If stack checking is turned on.) In fact, you would have to do quite a bit of twaddling to make this work (This is written off of the top of my head), but I bet it would be easier than rewriting the whole of a recursive-descent parser. In fact, a possibly more useful way to do this for your particular problem might involve keeping track of how much space is being used in the stack (via _SS and _stklen?) and then modify part of your parser's mutual recursion to work like: void RecursiveFunction(some parameters) { int stacksaved; if (very little stack space left) { save old _SS, point _SS at the _end_ of some (unused!) buffer. stacksaved = 1; } do whatever this function is supposed to do, possibly involving recursive calls. if (stacksaved) /* This part must happen before *EVERY* return */ restore old _SS; /* from this function. You can't return from this */ return; /* function without having done this!!! */ } Anyway, I hope this helps. If you actually succeed in getting this to work, I'd be interested in seeing the relevant chunks of your source code, so that I know the _exact_ way to do it instead of vague generalities like those above. Good luck! Derek Riippa Foster
rhys@batserver.cs.uq.oz.au (Rhys Weatherley) (10/31/90)
dfoster@jarthur.Claremont.EDU (Derek R. Foster) writes: >In article <1990Oct26.223541.26634@NCoast.ORG> catfood@NCoast.ORG (Mark W. Schumann) writes: >>DLV101@psuvm.psu.edu (Dwaine VanBibber) writes: >>>Does anyone have some general rules for porting C programs developed under >>>UNIX to MS-DOS? [ ... etc ... ] >Although this is generally true in "nice" programs, there may be a somewhat >kludgy workaround for this. > [ ... code to swap stacks, and do other ugly things ... ] EEK!! This solution should be applied with "fingers crossed"! I won't go into the code. Answering the original question, looking at lots of Unix programs, you find that many of them allocate big buffers and things on the stack. For example: char buffer[BUFSIZ]; This is "plenty fine" on Unix boxes where most everything is 32 bit, with HUGE stacks. However on MS-DOS machines, you of course (as pointed out earlier) only have 64K of stack, at best. However, just change the above line to: static char buffer[BUFSIZ]; And "hey presto", you've decreased the stack usage in a big way, by putting the buffer into the global data space. Also, with recursive functions (as usually present in parsers), the buffers soon mount up. Using this (which authors should do anyway for portability) is much "cleaner" than swapping stacks and stuff. There is one caveat: you must be aware that you DON'T have a new copy of the buffer each time in a recursive routine (it's always the same one). Careful inspection of the code can help you out greatly. This is not a "total" solution, but preferred to stack swaps wherever possible. Rhys. P.S. Derek, you changed _SS, but forgot about _SP. Weird things could happen! +===============================+==============================+ || Rhys Weatherley | University of Queensland, || || rhys@batserver.cs.uq.oz.au | Australia. G'day!! || +===============================+==============================+
cage@fmeed1.UUCP (Russ Cage) (10/31/90)
I tackled the porting of some MIT music synthesis code from VAX over to MSC on a PC/AT a few years ago. The biggest problem was (deja vu?) VAX-hackers allocating enormous arrays as automatics. (A pair of 10,000 element float arrays, for example...) Anyway, Craig Harris wanted it done, so I did it. MSC requires HUGE pointers and use of halloc() (if memory serves, it has been a while) to reference objects that big. But the code can be re-written with a bit of care and it can be made to work. The second biggest hassle was the penchant of VAXhackers for de-referencing NULL. Aside from whupping them upside the head with an ASR-33, I have no solution for that. -- Russ Cage Ford Powertrain Engineering Development Department Work: itivax.iti.org!cfctech!fmeed1!cage (CHATTY MAIL NOT ANSWERED HERE) Home: russ@m-net.ann-arbor.mi.us (All non-business mail) Member: HASA, "S" division.
lsalomo@hubcap.clemson.edu (lsalomo) (11/01/90)
If you extend the switching stacks idea a little further, you can implement a multi-tasking OS kernal rather easily. Do not try this at home: ;) Intercept the timer interrupt to count the number of time clicks the current process has been running for. When it reaches a terminal count, generate an interrupt to call the dispatcher. The dispatcher's job is to remove the process in the run queue and place it in ascending order (by priority) in the ready queue. It then takes the first item from the ready queue (==the highest priority) and places it in the run queue. THEN (here's the tricky part) it switches stack segments from the one which belonged to the last running process to the stack segment of the next process to run. When all of the interrupts unwind, the point in the new process where it was interrupt gets popped off into the registers, and the new process starts executing where it left off. = = = = = = = = Off course, the only tricky part is loading the .EXE's from disk and initializing the stacks to point to their entry point. = = = = = = = = If you'd like to discuss this further, contact me offline... Cheers, Q - the "Q"uestor for knowledge (, a degree, etc.) lsalomo@hubcap.clemson.edu ibmman@prism.clemson.edu ibmman@clemson.clemson.edu ============================================================================= "Gee Wally, I think there's something wrong with the Beaver." =============================================================================
stever@Octopus.COM (Steve Resnick ) (11/01/90)
In article <9449@jarthur.Claremont.EDU> dfoster@jarthur.Claremont.EDU (Derek R. Foster) writes: [Stuff deleted] > >Although this is generally true in "nice" programs, there may be a somewhat >kludgy workaround for this. DISCLAIMER: I haven't tried the following >suggestion. This is all theory on my part, and I would be interested in >hearing comments on it. Also the code I present is sketchy at best, and >is probably outright wrong in some respects. Don't expect it to work; >just use it as a model. > >Anyway, you might try changing the stack segment pointer to point into >a new segment of your own when the old one gets filled. For instance, >if you can identify a particular part of the program which will be >called only when the stack is almost filled, you might try something >like ... > >void huge char ABuffer[(some fancy calculation probably involving _stklen > to preserve the ability to detect stack overflow) + 16 (the 16 is to > ensure that I can always get a buffer of at least the size I want > which starts on an even paragraph boundary)]; > Warning Will Robinson! :) There is nothing to prevent you from changing the stack on a '86 processor, but you *cannot* access more than 64K. Even though TC supports huge pointers and deals with the additional segment arithmetic, the processor doesn't. Eg, PUSH/POP instructions will fail on a segment wraparound. If you do change the stack segment and pointer to a new address you cannot do any stack checking since the stack segement is different than the one defined by the C runtime startup routines. >/* note that ABuffer is not meant to be a huge pointer to an array of > chars, but a pointer to an array of 'huge chars'. This means it will > be allocated its own data segment. I *THINK* this is the right syntax...*/ > >void StackSwapFunction(void) >{ > unsigned oldSS; > > oldSS = _SS; You *MUST* disable interrupts during this operation. If an interrupt occures during the stack segment transition, you will not return from the interrupt correctly and crash the machine. > _SS = FP_SEG(ABuffer+sizeof(ABuffer)); > > Do other function calls here.... possibly if they get too deep you could > even do this same strategy with another buffer. Might even consider > dynamic allocation of such buffers. Just remember that the stack segment > has to start on an address that's a multiple of 16... > > _SS = oldSS; >} > >All functions called by this function will put their return >addresses, local variables, etc. in the new stack space, which is in the >buffer. You may have other housekeeping tasks to do as well, possibly >involving changing some global variables that are used internally by >TC to detect end-of-stack. (If stack checking is turned on.) In fact, >you would have to do quite a bit of twaddling to make this work (This >is written off of the top of my head), but I bet it would be easier than >rewriting the whole of a recursive-descent parser. > >In fact, a possibly more useful way to do this for your particular problem >might involve keeping track of how much >space is being used in the stack (via _SS and _stklen?) and then modify >part of your parser's mutual recursion to work like: > >void RecursiveFunction(some parameters) >{ > int stacksaved; > > if (very little stack space left) > { > save old _SS, point _SS at the _end_ of some (unused!) buffer. > stacksaved = 1; > } > > do whatever this function is supposed to do, possibly involving > recursive calls. > > if (stacksaved) /* This part must happen before *EVERY* return */ > restore old _SS; /* from this function. You can't return from this */ > return; /* function without having done this!!! */ >} > >Anyway, I hope this helps. If you actually succeed in getting this to >work, I'd be interested in seeing the relevant chunks of your source >code, so that I know the _exact_ way to do it instead of vague >generalities like those above. > Hope this helps..... Steve -- ---------------------------------------------------------------------------- steve.resnick@f105.n143.z1.FIDONET.ORG - or - apple!camphq!105!steve.resnick Flames, grammar errors, spelling errrors >/dev/nul The Asylum OS/2 BBS - (408)263-8017 IFNA 1:143/105.0
dfoster@jarthur.Claremont.EDU (Derek R. Foster) (11/01/90)
In article <5497@uqcspe.cs.uq.oz.au> rhys@batserver.cs.uq.oz.au writes: >dfoster@jarthur.Claremont.EDU (Derek R. Foster) writes: >>In article <1990Oct26.223541.26634@NCoast.ORG> catfood@NCoast.ORG (Mark W. Schumann) writes: >>>DLV101@psuvm.psu.edu (Dwaine VanBibber) writes: >>>>Does anyone have some general rules for porting C programs developed under >>>>UNIX to MS-DOS? [ ... etc ... ] > >>Although this is generally true in "nice" programs, there may be a somewhat >>kludgy workaround for this. >> [ ... code to swap stacks, and do other ugly things ... ] > >EEK!! This solution should be applied with "fingers crossed"! Yes, definitely! >[mention of using static buffers instead of automatic buffers when possible] > >This is not a "total" solution, but preferred to stack swaps wherever >possible. > >Rhys. > >P.S. Derek, you changed _SS, but forgot about _SP. Weird things could happen! Quite true. I didn't realize that I had forgotten to set _SP in my code. I might even venture to say that weird things WOULD happen. (especially since I set _SS to where _SP was supposed to go...I must have been half-asleep that night! As written, the stack tromps all over non-buffer memory!) But then I did say that the code I presented almost certainly wouldn't work :-) My code was mostly presented as a general sketch of what could be done, not how precisely to do it. I'd rather leave that part to someone who's done it before. Anyway, your method is certainly preferable to mine provided the savings is adequate. I certainly don't endorse stack swaps. I just think that as an emergency alternative to rewriting a large program, they have their place, ugly / nonportable though they are. I would avoid them whenever humanly possible, but it's a good idea to know that they are possible for those rare moments when a kludge is desperately needed. >+===============================+==============================+ >|| Rhys Weatherley | University of Queensland, || >|| rhys@batserver.cs.uq.oz.au | Australia. G'day!! || >+===============================+==============================+ Derek Riippa Foster
dfoster@jarthur.Claremont.EDU (Derek R. Foster) (11/01/90)
In article <1990Oct31.190520.11526@Octopus.COM> stever@octopus.UUCP (Steve Resnick ) writes: >In article <9449@jarthur.Claremont.EDU> dfoster@jarthur.Claremont.EDU (Derek R. Foster) writes: > >[Stuff deleted] >> >>Although this is generally true in "nice" programs, there may be a somewhat >>kludgy workaround for this. DISCLAIMER: I haven't tried the following >>suggestion. This is all theory on my part, and I would be interested in >>hearing comments on it. Also the code I present is sketchy at best, and >>is probably outright wrong in some respects. Don't expect it to work; >>just use it as a model. >> >>Anyway, you might try changing the stack segment pointer to point into >>a new segment of your own when the old one gets filled. For instance, >>if you can identify a particular part of the program which will be >>called only when the stack is almost filled, you might try something >>like ... >> >>void huge char ABuffer[(some fancy calculation probably involving _stklen >> to preserve the ability to detect stack overflow) + 16 (the 16 is to >> ensure that I can always get a buffer of at least the size I want >> which starts on an even paragraph boundary)]; >> >Warning Will Robinson! :) There is nothing to prevent you from changing the >stack on a '86 processor, but you *cannot* access more than 64K. Even >though TC supports huge pointers and deals with the additional segment >arithmetic, the processor doesn't. Eg, PUSH/POP instructions will fail >on a segment wraparound. Although this is technically true, the way you have phrased it is somewhat misleading. The limit is 64k PER STACK SEGMENT. The technique I am using first fills up the "default" stack segment, <= 64k, then switches to a new one when that one is full, also <= 64k, for a resultant "effective" stack size of >64k. So even though the stack pointer (which, as another poster pointed out, I forgot to mention in my sample code) can never be farther away from _SS than 64k, we could build an indefinitely long "linked list" of stacks, just as long as we remember to switch from one to the other when we are in danger of over/underflowing the one we are in. We should never have to deal with PUSHes and POPs over a segment boundary during this process, since if the stack pointer gets close to one, we're supposed to switch to a new stack. > >If you do change the stack segment and pointer to a new address you cannot >do any stack checking since the stack segement is different than the one >defined by the C runtime startup routines. > Not necessarily... I think it is _highly_ likely that _stklen measures the height of the stack relative to the stack segment register, and not to some absolute position in memory. (After all, what would be the point of using the segment value of a quantity if it's never supposed to change, and you always have a (normally constant) register pointing at it?) As such, if each "link" in the "linked list" is of a size to accomodate _stklen, then if we overflow the current stack segment by exceeding _stklen, a "stack overflow" message is printed, but if we instead switch to a new stack and reset the stack pointer, we can keep adding to the new stack until we are again in danger of exceeding _stklen relative to the _new_ stack segment. Thus the stack checking routines perform their intended purpose, sort of, namely detecting a program trying to write stack data outside of any stack segment. >> oldSS = _SS; > > You *MUST* disable interrupts during this operation. If an interrupt > occures during the stack segment transition, you will not return > from the interrupt correctly and crash the machine. True. Definitely disable interrupts. Oh, incidentally, another bug in my example code: don't store the old _SS in an automatic (local) variable. Since this variable actually resides on the first stack, once you change it, you have no way to access the old value. Use a static variable instead. In fact, remember that changing _SS and/or _SP will make a function "forget" ALL its local variables' values until the old values of _SS and _SP are restored. Perhaps a better way to do my second example would be to have a function which has no other purpose than to swap the stacks, call other functions, then swap the stacks back. The calling routine would then decide whether to call the other functions directly, or to make a call through the swapping function. (The decision being made by determining how much stack space is left in the current segment.) This way you wouldn't have to worry about losing the values of various local variables over the course of a stack swap. Also, for the same reason, don't even THINK of trying this technique in a memory model with near pointers. You won't be able to refer to local variables of previously-called functions by address if you do, since they may be in a different segment... Bad Things would happen. >steve.resnick@f105.n143.z1.FIDONET.ORG - or - apple!camphq!105!steve.resnick >Flames, grammar errors, spelling errrors >/dev/nul >The Asylum OS/2 BBS - (408)263-8017 IFNA 1:143/105.0 I hope this clarifies things a little. Thanks for the input. Derek Riippa Foster
cage@fmeed1.UUCP (Russ Cage) (11/02/90)
In article <9478@jarthur.Claremont.EDU> dfoster@jarthur.Claremont.EDU (Derek R. Foster) writes: >.... The technique I am using first fills up the "default" >stack segment, <= 64k, then switches to a new one when that one is >full, also <= 64k, for a resultant "effective" stack size of >64k. There are two serious problems with this. 1.) Unless all pointers are 'far', automatic variables from one function will not be accessible to functions past the stack-overflow mark; they will be in a different segment. 2.) Unless careful copying of data from stack frame to stack frame is done on stack overflow, parameters to the called function could get lost. Worse, the size of the parameter list in C is not known to functions such as printf()! In my experience, it is easier to re-write the code to remove the features which require excessive stack space. -- Russ Cage Ford Powertrain Engineering Development Department Work: itivax.iti.org!cfctech!fmeed1!cage (CHATTY MAIL NOT ANSWERED HERE) Home: russ@m-net.ann-arbor.mi.us (All non-business mail) Member: HASA, "S" division.
REIDMP@MAINE.BITNET (11/03/90)
If you have lots of memory to play with for code space, one really simple solution to try is to make as many of the functions inline as possible. Another simple fix that might work with TC (I can't recall for sure how the compiler treats variable declarations, but this works on many ANSI C compilers), is to delay declarations until the block you will use the variable in. Example: Before: After: void foo(char * s) { void foo(char * s) { int i; if (s) { if (s) { int i; i = 1; i = 1; } } ... ... } } The example is very simple and brain-dead, but the point is this: since variables reside (mostly) on the stack, and (generally) live until the end of the block of their declaration, push them further down into the nested blocks, particularly if the variables sometimes won't be used. This way you shorten the average life of data on the stack and sometimes avoid unused variables ever being created. With deeply nested recursion this can make a difference. However, this will only work if the compiler doesn't optimize out the declaration to do one single stack adjustment when the function is entered. ciao for now Reid
bcw@rti.rti.org (Bruce Wright) (11/11/90)
In article <1990Oct31.190520.11526@Octopus.COM>, stever@Octopus.COM (Steve Resnick ) writes: > In article <9449@jarthur.Claremont.EDU> dfoster@jarthur.Claremont.EDU (Derek R. Foster) writes: > > >void StackSwapFunction(void) > >{ > > unsigned oldSS; > > > > oldSS = _SS; > > You *MUST* disable interrupts during this operation. If an interrupt > occures during the stack segment transition, you will not return > from the interrupt correctly and crash the machine. > > > > _SS = FP_SEG(ABuffer+sizeof(ABuffer)); This is misleading. It's not necessary to disable interrupts when swapping stack segments if you do it right! The point is that the 80x86 processors disable interrupts for one instruction after loading the SS register (some of them disable interrupts for one instruction after loading ANY segment register, but later processors limited it to SS - I forget the exact details of which processors behave which way because it's usually not important). So if the next instruction IMMEDIATELY after loading the new value into SS loads the new value into SP, then you're golden and don't need to mess around with manually disabling interrupts. Look in any of the processor handbooks for the Intel processors. I don't know enough about Turbo C++ to know whether it gets in your way and inserts other instructions after the assignment to SS, but it does work fine in assembler. On the subject of swapping the stack, another place that it's often needed is for interrupt service routines running in real mode - in this mode the 80x86 processor does not perform the interrupt on a separate stack but on whatever stack the application program is using. An amazing number of application programs have allocated EXACTLY the amount of stack that they are going to be using, and there isn't enough room to even save the registers for the interrupt service routine. (Note that they may have taken into account the amount of stack space required by any BIOS calls that they make, so that your interrupt may be OK until you interrupt a BIOS call). Anyway, if you are writing a real mode interrupt handler it's a real good idea to swap stacks to a local stack while using as little of the application program's stack as possible; it can be useful to know that when swapping in either direction you don't have to worry about whether you have 80x86 interrupts enabled when you move the segment as long as you do things in the proper order. Bruce C. Wright
hill@netcom.UUCP (Tom Hill) (11/11/90)
In article <1990Nov10.223333.4628@rti.rti.org> bcw@rti.rti.org (Bruce Wright) writes: ... >> > oldSS = _SS; >> >> You *MUST* disable interrupts during this operation. If an interrupt >> occures during the stack segment transition, you will not return >> from the interrupt correctly and crash the machine. > >This is misleading. It's not necessary to disable interrupts when >swapping stack segments if you do it right! The point is that the >80x86 processors disable interrupts for one instruction after loading >the SS register (some of them disable interrupts for one instruction >after loading ANY segment register, but later processors limited it >to SS - I forget the exact details of which processors behave which >way because it's usually not important). So if the next instruction >IMMEDIATELY after loading the new value into SS loads the new value >into SP, then you're golden and don't need to mess around with manually >disabling interrupts. Sorry, but this is only correct for the later intel processors. The original 8088s did not disable the interrupts after a load of SS, thus requiring thta interrupts be disabled. If you know your code will only run on ATs & up, sequential loads of SS & SP work fine. If you want to be sure your code will run on your occasional original PC, disable the interrupts. Tom Hill