The Pexec Cook Book - From the Web ================================== Many people asked for the Pexec Cookbook or wondered what it was. What I have and hereby post is a "preliminary version of the long-awaited Pexec cookbook". It was posted already some time ago ("those good old days") by Allan Pratt (Atari) in this newsgroup. I do not know if a newer version exists. - Appended to the Pexec Cookbook you will find some more comp.sys.atari.st articles on the subject of program calling. Hopefully you will find some useful information there! Dipl.-Inform. Rainer Klute klute@heike.informatik.uni-dortmund.de Univ. Dortmund, IRB klute@unido.uucp, klute@unido.bitnet =================================== Allan Pratt (Atari) on the subject of Pexec:- This is in response to a request from Christian Kaernbach which I got from BITNET: I can't reply directly to BITNET, but I'm sure other people will find this interesting, too: it's a preliminary version of the long-awaited Pexec cookbook! In broad terms, the things you have to know about Pexec are that it starts up a process, lets it execute, then returns to the caller when that process terminates. The "caller" -- the process which used Pexec in the first place -- has some responsibilities: it has to make memory available to the OS for allocation to the child and it has to build up the argument string for the child. All GEMDOS programs are started with the largest block of OS memory allocated to them. Except in very rare circumstances, this block is the one stretching from the end of the accessories and resident utilities to the beginning of screen memory. The point is that your program has probably been allocated ALL of free memory. In order to make memory available for a child process, you have to SHRINK the block you own, returning the top part of it to GEMDOS. The time to do this is when you start up. If you use Alcyon C (from the developer's kit), you know that you always link with a file called GEMSTART. If you've been paying attention, you should have gotten the *new* GEMSTART from Compuserve (or from somebody else who got it): I wrote that GEMSTART. In GEMSTART.S, there is a lot of discussion about memory models and then a variable you set telling how much memory you want to keep or give back to the OS. Make your choice (when in doubt, use STACK=1), assemble GEMSTART.S, call the result GEMSEXEC.O (or something), and link the programs which Pexec with that file rather than the normal GEMSTART. Now here's a discussion of what GEMSTART has to do with respect to keeping or returning memory: Your program is invoked with the address of its own basepage as the argument to a function (that is, at 4(sp).l). In this basepage is the structure you can find in your documentation. The interesting fields are HITPA (the address of the first byte NOT in your TPA), BSSBASE (the first address of your bss) and BSSLEN (the length of your BSS). Your stack pointer starts at HITPA-8 (because 8 is the length of the basepage argument and the dummy return PC on the stack). The space from BSSBASE+BSSLEN to your SP is the "stack+heap" space. Library malloc() calls use this space, moving a pointer called the "break" (in the variable __break, or the C variable _break if you use Alcyon C) up as it uses memory. Your stack pointer moves down from the top as it uses memory, and if the sp and _break ever meet, you're out of memory. In fact, if they ever come close (within a "chicken factor" of about 512 bytes or 1K), malloc() will fail because it doesn't want your stack to overwrite good data. When a process starts, it gets *all* of memory allocated to it: from the end of any accessories or resident utilities up to the default screen memory. If you want to use Pexec, you have to give some memory back to the OS. You do this with the Mshrink call. Its arguments are the address of the memory block to shrink (your basepage address) and the new size to shrink it to. You should be sure to leave enough room above your BSS for a reasonable stack (at least 2K) plus any malloc() calls you expect to make. Let's say you're writing "make" and you want to leave about 32K for malloc() (for your dependency structures). Also, since make is recursive, you should leave lots of space for the stack - maybe another 16K. The new top of memory that your program needs is: newtop = your bss base address + your bss size + 16K stack + 32K heap Since your stack pointer is at the top of your CURRENT TPA, and you're about to shrink that, you'd better move your stack: move.l newtop,sp Now you want to compute your new TPA size and call Mshrink: move.l newtop,d0 sub.l basepage,d0 ; newtop-basepage is desired TPA size move.l d0,-(sp) ; set up Mshrink(basepage,d0) move.l basepage,-(sp) move.w #$4a ; fn code for Mshrink trap #1 add.l #10,sp ; clean up args Now that you've shrunk your TPA, the OS can allocate this new memory to your child. It can also use this memory for Malloc(), which is used occasionally by GEM VDI for blt buffers, etc. Note that you only have to do this once, when you start up: after that, you can do as much Pexec'ing as you want. When you want to exec a child, you build its complete filespec into one string and its arguments into another. The argument string is a little strange: the first character of the argument string is the length of the rest of the string! Here is a simple system call: pass it the name of the file to execute and the argument string to use. long system(cmd,args) char *cmd, *args; { char buf[128]; if (strlen(args) > 126) { printf("argument string too long\n"); return -1; } strcpy(buf+1,args); /* copy args to buffer+1 */ buf[0] = strlen(args); /* set buffer[0] to len */ return Pexec(0,cmd,buf,0L); } The first zero in the Pexec call is the Pexec function code: load and go. The cmd argument is the full filespec, with the path, file name, and file type. The third argument is the command-line argument string, and the fourth argument is the environment pointer. A null environment pointer means "let the child inherit A COPY OF my environment." This call will load the program, pass the arguments and environment to it, and execute it. When the program terminates, the call returns the exit code from the program. If the Pexec fails (not enough memory, file not found, etc,) a negative code is returned, and you should deal with it accordingly. Note that error returns from Pexec are always negative LONGS, while return codes from the child will have zeros in the upper 16 bits. EXIT CODES: GEMDOS, like MS-DOS before it, allows programs to return a 16-bit exit code to their parents when they terminate. This is done with the Pterm(errcode) call. The value in errcode is passed to the parent as the return value of the Pexec system call. The C library function exit(errcode) usually uses this call. Unfortunately, the people who wrote the startup file for the Alcyon C compiler didn't use this. The compiler calls exit() with an error code, and exit() calls _exit(), but _exit always uses Pterm0(), which returns zero as the exit code. I fixed this by rewriting GEMSTART.S, the file you link with first when using Alcyon. Even though new programs return the right exit code, the compiler itself still doesn't. Well, I have patched the binaries of all the passes of the compiler so they DO. It isn't hard, and I will post instructions at a later date for doing it. IF YOU DO THIS, PLEASE DON'T BOTHER OUR CUSTOMER SUPPORT PEOPLE IF IT DOESN'T WORK. THEY DON'T KNOW ANYTHING ABOUT IT. I hope that this little cookbook makes Pexec less mysterious. I haven't covered such topics as the critical-error and terminate vectors, even though they are intimately connected with the idea of exec'ing children. A more complete cookbook should be forthcoming. If there are any errors or gross omissions in the above text, please let me know BY MAIL so I can correct them coherently. Landon isn't here to check my semantics, so I may have missed something. [Landon is on vacation in France until early September.] ******************************************************************** C. Kaernbach's question was why his accessory, which basically did a Pexec from a file selector, didn't always work. The answer is that it works when used within a program which has returned enough memory to the OS for the child. Why might it bomb? Because if a program has returned a *little* memory to the OS (only about 2K), a bug in Pexec shows up that breaks the memory manager. Accessories are strange beasts anyway, so for the most part combining two strange beasts (Accessories and Pexec) is bad news. /--------------------------------------------\ |Opinions expressed above do not necessarily | -- Allan Pratt, Atari Corp. |reflect those of Atari Corp. or anyone else.| \--------------------------------------------/ (APRATT on GEnie) In article <767@saturn.ucsc.edu>, koreth@ssyx.ucsc.edu (Steven Grimm) says: #include main() { long basepage; basepage = Pexec(3, "test.tos", "", 0L); printf("basepage = %08lx\n", basepage); Pexec(4, 0L, 0L, basepage); printf("done\n"); } The correct call for Pexec type 4 is "Pexec(4,0L,basepage,0L);" The combination of load/nogo plus just-go does not work reliably. There are bugs in Pexec relating to memory ownership which cause trouble. You should be able to determine something about where the bombs are, though: is the PC in ROM, or less than "basepage" (i.e. in the parent), or greater than basepage (i.e. in the child)? I have a trick which *does* work for fooling with a child before it has started executing, but it is ugly. I use it in my debugger. What are you trying to do? Maybe the same trick would work for you. Mail me more explanation. /--------------------------------------------\ |Opinions expressed above do not necessarily|-- Allan Pratt, Atari Corp. |reflect those of Atari Corp. or anyone else.| \---------------------------------------------/ 1322@dasys1.UUCP>, stevef@dasys1.UUCP (Steve A. Feinstein) says: Does anyone know if there is any way to, say load a program into the top of the TPA and execute it up there. So that when it exits, there won't be any holes? There is one way to do this with Pexec, but it requires knowing how much memory the program will need (lots of heuristics here). long topPexec(cmd,args,env,size) char *cmd, *args, *env; unsigned long size; unsigned long blocksize; char *addr; long ret; blocksize = Malloc(-1L); /* find out size of largest block */ if (block < size) { return ENSMEM; /* largest block not big enough! */ } addr = Malloc(blocksize); /* allocate that block */ Mshrink(addr,blocksize-size); /* shrink it by size TSR needs */ ret = Pexec(0,cmd,args,env); /* exec the TSR */ Mfree(addr); /* free the padding */ return ret; /* return the exit code */ This procedure takes a "size" argument which is the amount of memory the program in question will need for its text, data, bss, basepage, environment, and stack/heap. This is not a computable number: you have to figure it out empirically. text+data+bss+env+10K or so should work, but you will always leave a small hole at the top of memory, because TSR's always Mshrink a little bit. ============================================ Opinions expressed above do not necessarily -- Allan Pratt, Atari Corp. reflect those of Atari Corp. or anyone else. ============================================ Attention Mark Williams, Beckmeyer, and Gert Poltiek, and anybody else interested: There is a trick that some shells and compiler libraries use that lets you pass argument strings to programs which are longer than the 127 bytes which fit in the command line area of the basepage. Their trick is to put the word ARGV= in the environment, and follow it with a null-separated list of argument strings. The list is terminated with another null. This scheme works pretty well, but has two drawbacks, one major and one minor. The minor drawback is that it defies the definition of what is in the environment: the environment should consist of strings of the form NAME=value terminated by a final. This is minor because shells using this convention usually put the ARGV information at the end of the environment anyway. The major drawback is that you can't tell if the ARGV string in your environment is really meant for you. Imagine you have the Mark Williams shell (msh), an editor compiled with Alcyon, and another utility like "echo" compiled with MWC. Imagine further that the editor has a "shell- escape" command that lets you execute another program from within the editor. Do this: From msh (the MWC shell): start up the editor with the command line arguments "this is a test." Tell the editor to execute the command "echo hello world." The "echo" command will echo "this is a test," not "hello world." What happened is that msh put "this is a test" in the environment for the editor (as well as in the command tail in the basepage). The editor, not knowing any better, didn't put "hello world" in the environment before executing "echo." When "echo" started, it found "ARGV=this is a test" in its environment and echoed that. What is needed is a way for a program to tell if the "ARGV=" string in its environment is really intended for it, or is just left over from an earlier program. There is a way to do this that doesn't affect old programs compiled without this fix. The new convention could be to place another string in the environment with your own basepage address, before Pexec'ing your child. The child could start up, and check to see if its parent's basepage address (in its basepage) matches the address in the environment. If it does match, the child will know that the ARGV= string is for it. If it doesn't match, the child will know it was started from a non-MWC program like the editor above, and will look in its basepage for the command line. Note that if the parent's basepage isn't in the environment at all, but the ARGV= string is, the child must assume that the ARGV string is intended for it, just as it does now. Therefore, old-style programs could still Pexec new-style children, and vice-versa. This would all require a change in the startup code that calls main(), and the exec() code which Pexec's the child. How about it, guys? If we could all agree on the name and format of this new environment variable, we could get rid of a serious flaw in Mark Williams' otherwise clever scheme. Other shells could adopt this, too, and ultimately everybody would be able to kiss the 127-character command-line limit goodbye. For now, I propose that the environment variable in question be called PBP, and that its value be the decimal string of digits making up the parent's basepage. The reason for this is that almost all libraries have an atol() function, where not all have an atolx() function. A shell using this trick, with a basepage at 366494 (decimal), could Pexec a child called "test.prg" with these strings in the environment: ... PBP=366494 ARGV=test.prgfirstsecondthird In the startup code of the child, you would do something like this: If there's a PBP= in the environment If atol(PBP) == my parent's basepage get args from environment else get args from command line endif else if there's an ARGV= in the environment get args from environment else get args from command line endif endif Does this sound reasonable? I would like to see this kind of thing become a standard, but until a safeguard like this is in place, I can't condone using ARGV= in the environment for finding your arguments. It's too chancy just to assume that you were started by a program savvy to this scheme. /----------------------------------------------\ |Opinions expressed above do not necessarily |-- Allan Pratt, Atari Corp. |reflect those of Atari Corp. or anyone else. | \----------------------------------------------/ ==================================== The End ==================================== Article 543 of comp.sys.atari.st: Path: Subject: shell p usage Message-ID: <326@stag.UUCP> Date: 3 Feb 88 14:17:46 GMT Article-I.D.: stag.326 Posted: Wed Feb 3 15:17:46 1988 Sender: daemon@stag.UUCP Lines: 176 writes... Question #1 : How MWC's msh is discovering who called it ? (desktop, or other...) it is in the local shell variable 'calledfrom'. Any other shell is able to tell the list of all parents programs... The base page does not contain any backward pointer, so what is it ? I'm not sure that MSH does* detect being called from the desktop. What difference would it make (or does it in MSH). The basepage, however, DOES* contain a "backward pointer" to the parent processes basepage. Here is a struct to clarify... typedef struct { char *p_lowtpa; /* pointer to self (bottom of TPA) */ char *p_hitpa; /* pointer to top of TPA + 1 */ char *p_tbase; /* base of text segment */ long p_tlen; /* length of text segment */ char *p_dbase; /* base of data segment */ long p_dlen; /* length of data segment */ char *p_bbase; /* base of BSS segment */ long p_blen; /* length of BSS segment */ char *p_dta; /* pointer to current DTA */ char *p_parent; /* pointer to parent's basepage */ char *p_reserved; /* reserved for future use */ char *p_env; /* pointer to environment string */ long p_undefined[20]; /* scratch area... don't touch */ char p_cmdlin[128]; /* command line image */ } BASEPAGE; Question #2 : I read again the dev kit doc on shell_p, the posting by A. Pratt about it, and I still wonder : what is it ? I would infer it is a pointer to a string telling the file name of a shell... I am right ? What would be the use otherwise ? The shell_p variable, if it is valid, should point to a routine which will take a string argument and process it like the Un*x system() call. Here is an example implementation of system() which tries to use the shell_p variable, if possible. /*----------------------------------------------------------------------*/ #include #include #include #include static parse_args(cmdln, argv) char *cmdln; register char *argv[]; { register char *p; static char delim[] = " \t\r\n"; if(p = strtok(cmdln, delim)) { do { *argv++ = p; } while(p = strtok(NULL, delim)); } } int system(command) register char *command; /* * Attempts to pass to the shell program pointed to by * the system variable "_shell_p". If a valid shell can't be found * there, the "SHELL" environment variable is searched for. If it * exists and is not empty, it will be the name of the shell program * to execute the given command. If "SHELL" is not valid, the * "PATH" variable is used as a list of directories to search for * the program name which is the first token of the command. The * extensions tried (if none is specified) are ".TTP", ".TOS", * ".PRG" and ".APP". */ register char *p; register int (*shell)(); char rv[2]; char cmdln[1024]; char *args[64]; char *getenv(); if(!command) return(ERROR); /* get _shell_p value */ p = Super(0L); shell = *((long *) 0x4F6L); Super(p); /* validate _shell_p */ if((shell) && /* Shell available. */ (((long) shell) < ((long) _base)) && /* Reasonable shell pointer. */ (strncmp(shell, "PATH", 4))) /* Not corrupted */ { /* execute the command */ return((*shell)(command)); } /* copy the command line for parsing */ strcpy(cmdln, command); /* SHELL= variable? */ if((p = getenv("SHELL")) && (*p)) { args[0] = p; parse_args(cmdln, args+1); forkvpe(p, args, NULL); wait(&rv); return((rv[1] == 0) ? rv[0] : rv[1]); } /* attempt to find first token as a program on the path */ parse_args(cmdln, args); if(p = pfindfile(args[0], ".ttp\0.tos\0.prg\0.app")) { forkvpe(p, args, NULL); wait(&rv); return((rv[1] == 0) ? rv[0] : rv[1]); } return(ERROR); /*----------------------------------------------------------------------*/ I hope this answers your question. It's so easy for a shell to install itself on the shell_p vector and it's so convenient if other programs which support shell escapes use a system() call like the one above. In case there is any question about installing and removing the shell_p vector, the following routines will handle it nicely. /*----------------------------------------------------------------------*/ long (*sh_save)(); /* previous value of _shell_p variable */ sh_install(new_shell) int (*new_shell)(); /* * install _shell_p vector */ { register long *ssp; register (**shell_p)() = 0x4F6L; ssp = Super(0L); sh_save = *shell_p; *shell_p = new_shell; Super(ssp); } sh_restore() /* * restore old _shell_p vector */ { register long *ssp; register (**shell_p)() = 0x4F6L; ssp = Super(0L); *shell_p = sh_save; Super(ssp); } /*----------------------------------------------------------------------*/ PS. For those waiting for dLibs v1.1 and/or MicroEMACS 2.19, I have sent out disks for all the requests I had outstanding, you should either already have them, or should receive them soon. Sorry for the delays. I hope you're happy with the results. I'd like to hear from some of you after you've used dLibs a bit. I'd be happy to receive bug reports, suggested enhancements or just experiences so that I know they're being put to good use. Dale Schumacher (alias: Dalnefre') Article 616 of comp.sys.atari.st: Path: From: john@viper.Lynx.MN.Org (John Stanley) Newsgroups: comp.sys.atari.st Subject: Re: Print redirection routine Message-ID: <595@viper.Lynx.MN.Org> Date: 8 Feb 88 09:49:38 GMT Article-I.D.: viper.595 Posted: Mon Feb 8 10:49:38 1988 References: <8801271313.AA10750@lasso.laas.fr> <15@obie.UUCP> Reply-To: john@viper.UUCP (John Stanley) Organization: DynaSoft Systems Lines: 54 >In article <8801271313.AA10750@lasso.laas.fr>, >ralph@lasso.UUCP (Ralph P. Sobek) writes: >> Subject: Print redirection routine >> >> what I'm looking for is an assembler or C program which would redirect >> the output of one of these programs to an ASCII file rather than the printer. The best program to accomplish this is called BARREL written by Moshe Braner. It has several functions to allow capturing text or screen dumps to a file. It is TSR (terminate & stay resident) which may or may not be what you originally wanted, but it is good and it works.... In article <15@obie.UUCP> wes@obie.UUCP (Barnacle Wes) writes: >You can redirect the output to printer in your program via the following: > [part of sample deleted] > /* Now execute the program, the standard handles are inherited. */ > Pexec(0, "disk:/program/name/path.prg", NULL, tail); >.... > > Hope this helps whoever it was! Not likely... The example you gave will bomb miserably... The parameters you gave for Pexec are in the wrong order and ignore an important peculiarity in the Pexec call (the command tail is a Pascal style string, not a C style string). The correct order for Pexec parameters is: Pexec mode: zero is fine Program name: standard C string format Command tail: a pointer to a Pascal style string (a string with the character count in the first byte!) Environment: A pointer to an environment block or NULL if you want to use the default environment. Thus, a corrected version would read: static unsigned char tailbuf[255]; .... tailbuf[0] = strlen(tail); strcpy(tailbuf+1,tail); Pexec(0, "A:\\folder1\\folder2\\program.ttp", tailbuf, (char*)NULL); --- John Stanley (john@viper.UUCP) Software Consultant - From laura Tue May 3 13:24:35 MET DST 1988 Article 8601 of comp.sys.atari.st: From: leo@philmds.UUCP (Leo de Wit) Newsgroups: comp.sys.atari.st Subject: Re: preloading programs Message-ID: <474@philmds.UUCP> Date: 2 May 88 11:25:04 GMT References: <8804141742.AA01455@decwrl.dec.com <693@ast.cs.vu.nl> <390@uvicctr.UUCP> <188@obie.UUCP> Reply-To: leo@philmds.UUCP (L.J.M. de Wit) Organization: Philips I&E DTS Eindhoven Lines: 69 Posted: Mon May 2 12:25:04 1988 In article <188@obie.UUCP> wes@obie.UUCP (Barnacle Wes) writes: In article <390@uvicctr.UUCP>, collinge@uvicctr.UUCP (Doug Collinge) writes: >> Someone said that OS9 68k is capable of "preloading" commands; that is, >> loading some programs into memory and executing them there when invoked >> rather than loading them from disk. This seems to be yet another >> excellent idea incorporated in OS9 that no-one else seems to have >> thought of. Yes, that is one of the many good features of OS9. Another is pre-loaded modules. You can have "external" modules on OS9 - when [...stuff deleted...] itself. You can, of course, pre-load modules. Nice feature. >> How difficult would it be to hack this into Gulaam? Probably VERY difficult. TOS is not setup to handle pre-loaded programs; [...stuff deleted...] Never used gemdos call 0x4b (Exec) with a mode of 4? You can have preloaded programs on the Atari ST as well, and it works quite nicely. I used it in a find command and in a shell I programmed for the Atari (yes, the Unix find command!), for example: find .. -type d ! -name . ! -name .. -exec ls -l {}\; (For those not familiar with find, this one finds all directories beneath the current directory's parent (excluding . and .. names) and does a ls -l on each one of them). Each time loading ls worked fine on the ramdisk I used to use, but once I started working on a hard disk, it becomes really slooooooooooowwww! Then I decided to preload the ls command (with gemdos(0x4b,3,"ls",0,0)). The gemdos function returns the basepage address of the loaded program. Now 'find', each time it has to 'ls', copies the argument string into the basepage; then it runs 'ls' by gemdos(0x4b,4,0,0,0) (I don't remember whether these are the correct values for the parameters). It is now (nearly) as fast as a built in function! There are however a few problems (and I have not solved all of them): 1) When the parent program changes directories, the child stays in the directory it started in (the current directory and disk are saved somewhere on the basepage, start+55?). 2) There is also a problem with the memory; the loaded child obtains, as usual the rest of the memory available (in addresses basepage+4 to basepage+7 the address of the 'next free location' or something like that is found: 0xF8000 on my 520 ST+). Thus there is a problem with multiple preloaded childs or childs requesting additional memory. I've overcome this (sort of) by using setblock (gemdos 0x4a) and adjusting the 'next_free_location' ptrs on the childs basepages. It's better now, but still buggy (I don't know all about Gemdos memory management, I'm finding out now). 3) There could be problems as well with file descriptors (they're also on the basepage). 4) Initialized static and extern variables do not regain their initial value after a run. This is in consequence of the 'once-only' load. It could be overcome by a) always explicitly initialising (by the child process), or b) make a copy of the initialised data space (by the parent process) and use this to re-initiate from the next time (more expensive, but not breaking existing code). If you manage to overcome these difficulties, it should not be very hard to code this preloading into a shell (I did it, having still these little problems ...). The shell maintains a list of preloaded programs, their pathname and their fat number and handles loading/executing depending on memory available, programs preloaded etc. Preloaded programs are almost as fast as functions built into the shell. If anybody has suggestions about the memory management part, or questions 'about the real code', or want examples, please RESPOND! A commented disassembly of the gemdos part of the O.S. would also be great .... leo. From: leo@philmds.UUCP (Leo de Wit) Newsgroups: comp.sys.atari.st Subject: Re: preloading programs Keywords: Preload, Ramdisk, Pexec Message-ID: <482@philmds.UUCP> Date: 16 May 88 11:28:03 GMT References: <8804141742.AA01455@decwrl.dec.com> <693@ast.cs.vu.nl> 474@philmds.UUCP <219@obie.UUCP> Reply-To: leo@philmds.UUCP (L.J.M. de Wit) Organization: Philips I&E DTS Eindhoven Lines: 139 Posted: Mon May 16 12:28:03 1988 In article <219@obie.UUCP> wes@obie.UUCP (Barnacle Wes) writes: In article <474@philmds.UUCP>, leo@philmds.UUCP (Leo de Wit) writes: ...[stuff deleted]... One of the problems is that GEMDOS will release all of the memory allocated to program, including that gotten in the original load/Mshrink process, when the program exits via Pterm or Pterm0. I don't know if the Pexec(,4,,,) call re-allocates that memory or not. >> ...[stuff deleted]... I have had some time to figure things out (disassembled Pexec and several others) and managed to get a reusable load image in core that can be run over and over. A word of caution: the program should not depend on initialized data, unless this data will not change (is 'readonly'). An example: #define INTERACTIVE 1 int option = 0; main(argc,argv) int argc; char *argv[]; if ((argc > 1) && (!strcmp(argv[1],"-i"))) { option |= INTERACTIVE; } /* REST OF CODE ... */ After one run with -i flag option 'has the INTERACTIVE bit set' and will have it too on the next run. So option must be explicitly initialised: main(argc,argv) int argc; char *argv[]; option = 0; /* REST OF CODE ... */ Before I give the prescription I will give a more detailed explanation of the Pexec call, Pexec(mode,path,tail,env); mainly because most books about GEMDOS are much too terse about the subject (please correct me if I'm wrong, I had to find out things the hard way, like so many of us). Which functions will be performed within Pexec depends on the mode word; this can be either 0, 3, 4 or 5. The following actions are performed: 1) mode 0 and 3: searchfile. If the program file 'path' does not exist, Pexec returns error -32 (file not found). 2) mode 0, 3 and 5: 'basepage initializations': a) create environment string. If 'env' is null a copy of the parent's environment string is made, else a copy of 'env' is made. 'env' is terminated by two '\0's. The new environment space is allocated and bp[11] is set to point to it (consider bp a (char **), it is the start of the basepage). b) allocate program space. The maximum available space is claimed (that returned by Malloc(-1)). c) If mode == 0, the environment and program spaces are owned by the child (in the memory parameter blocks the owner is the new basepage); if mode == 3 or mode == 5, the parent owns the memory blocks. This fact ensures that the program area is not automatically reclaimed when the child exits (but space Malloced by the child is). d) bp[0] is set to the basepage, bp[1] is set to basepage + length of program block (i.e. the first location above the program). e) Initialise (redirected) file descriptors 0 - 5 (bytes 48 through 53 on the basepage). f) Initialise current working directories: for each drive one (bytes 64 through 79 on the basepage). Note that e) and f) involves more than simply assigning a value. g) Initialise current drive (byte 55 on the basepage). This is copied from the parent. h) Copy tail to basepage + 128 (max. 0x7d bytes). bp[8] is set to point to this location. 3) mode 0 and 3: load program. This is a function on its own, and I will not explain all details. Generally speaking, the function performs some checks on the program format, it loads program and data, does relocation, null fills all above data and sets bp[2]: text start, bp[3]: text length, bp[4]: data start, bp[5]: data length, bp[6]: bss start, bp[7]: bss length. 4) mode 0 and 4: final initialisations and run. a) bp[9] is set to the current basepage (parent). b) A save area for registers is initialised at the last 50 locations of the program; bp[30] and bp[31] are set to point to this area; bp[26] through bp[30] are for saving other registers. c) switch process: the new basepage is entered into the long at 0x602c and registers are loaded using the save area and bp[26]..bp[30] mentioned in b). So far for the 'preliminary meditations'; here's the recipe: A) First preload using mode 3: bp = (char **)Pexec(3,"program","",0); B) Return unused memory to the system: Calculate the memory needed, e.g. memneed = 0x100 + (int)bp[3] + (int)bp[5] + (int)bp[7] + 0x800 + 0x800; basepage text data bss heap stack. (the values needed for heap and stack depend on your program/your needs). Now: Mshrink(bp,mneed); /* return unused memory to the system */ Mfree(bp[11]); /* free the new environment space, we won't use it */ bp[1] = (char *)bp + mneed; /* first free location is lower now */ C) Prepare to run: bp2 = Pexec(5,0,"arguments",0); Mshrink(bp2,0x100); /* only keep a basepage */ for (i = 1; i<8; i++) bp2[i]=bp[i]; /* set pointers and lengths correctly*/ D) Now run it: code = Pexec(4,0,bp2,0); Mfree(bp2[11]); /* frees the environment space */ Mfree(bp2); /* frees the new basepage */ For each run, do C) and then D). Hey, it works! Some remarks: a) The environment is not used (0, so the parent's is used). You can use an environment string, in C): Pexec(5,0,"arguments","environment\0"); note that the environment string must end on a double '\0', as in the example. b) Although the arguments string is simply copied, it is a convention that the first byte of the args string contains its byte count. This is probably due to the fact that the GEMDOS readline function (0xa) also returns the count in the string (and probably GEM uses this type of string for Pexec when starting TTP applications). So: char newtail[128]; strncpy(newtail,tail,0x7d); newtail[0] = strlen(newtail + 1); Pexec(5,0,newtail,0); c) The bp[..] and bp2[..] mentioned are longs (char *, to be precise). d) Preload could be used to provide more than 128 characters arguments string. The arguments can be placed on the child's stack; the child can find them using bp[8] (this will break existing code that uses simply (char *)bp + 128). I suggest that we use bp[8] as a char *[], as is argv; and use a startup module that uses this scheme if bp[8] != (char *)bp + 128 and the old one if it equals. e) A similar scheme can be used for the 'env', bp[11]. f) I'm sure d) and e) start up new discussions! Hope this clarifies some things about Pexec and makes it better usable. I'm waiting for your responses, questions, remarks, experiences... Leo (Swimming in the C..)