Mail Archives: djgpp-workers/2001/04/22/15:03:47
> Thanks a lot for digging into this. In the future, please send your
> messages wrt this matter to djgpp-workers AT delorie DOT com, which is a
> mailing list read by DJGPP developers (Charles Sandmann does not
> normally read it, so please keep him on the CC list).
ok, no problem. likewise, please keep mailing me on this issue as well
as i am not reading that mailing list (nor does that web archive seem
to be updated since the 17th).
> Just to set the record straight: does the rule run by Makefile indeed
> include only the above simple commands, or do you have some more
> arguments there?
first of all, the first line should have read 'cmd 1| cmd2', ie. only
two commands were piped, not 3 (i should not trust my memory ;-). as
for the commands, they of course all take various arguments, but i
do not think that matters (you will see later what i found out).
> The reason I'm asking is that I'm trying to understand how exactly did
> Make run those commands. Depending on the characters used by the
> rule's commands, it could run the programs either via `spawn' or via
> `system' library functions. If the Makefile says "SHELL = /bin/sh" or
> similar, there might be a further complication because Make might
> decide to run Bash and pass it the entire command line.
well, the makefile is a bit complicated, you should better take a look
at it yourself (http://ghiribizzo.virtualave.net/icedump/id6023.zip,
then w9x/makefile around the MAKELEVEL=1 part). the makefile itself
does not override SHELL however my djgpp.env does:
SHELL=%DJDIR%/bin/sh.exe
where sh.exe is a symlink to bash.exe. the versions of the various
executables (again, i do not think it matters):
GNU bash, version 2.04.7(1)-release (i386-pc-msdosdjgpp)
GNU Make version 3.79.1, by Richard Stallman and Roland McGrath.
Built for i386-pc-msdosdjgpp
> Do you have any idea whether the fact that the interrupts are nested
> have any significance here?
first of all, after some more poking around in ntvdm, i am not 100%
sure if they are really nested or it is just how the stack looks like
when ntvdm emulates the dpmi/dos environment (the stack trace i quoted
before was on the ntvdm internal stack, this is not the same as the
stack that the dpmi app uses or the locked pmode stack used for
reflecting certain interrupts).
in any case, i think this 'nestedness' issue is irrelevant, because by
the time it occurs, the stack which ntvdm wants to use for reflecting
the interrupt(s) is long gone (see below).
> > the last one of these interrupts seems to be some hardware irq
> > reflected to this ntvdm (i did not yet debug it to see which one).
>
> Probably the timer, unless you touched the keyboard. Do you have some
> non-standard devices on that machine which could trigger a hardware
> interrupt? A network adapter, perhaps?
i have a NIC, but it does not really matter what causes the interrupt,
once this special stack is gone, anything that ends up being reflected
will raise an exception (on my tests i observed both my mouse and even
plain int 0x21).
> Since this happens in nested DPMI programs, perhaps some part of NTVDM
> ``remembers'' that the stack was already set up, but some other part
> uncommits the memory pages because the previous image has exited?
excellent guess (took me a while to debug it though ;-), see below for
details.
> The most useful information would be to find out where is the EIP of
> the DJGPP program when this happens.
the particular interrupt that ntvdm wants to reflect is initiated by
a dpmi call from ___dpmi_int which in turn calls dpmi service 0x0300
(simulate real mode interrupt).
> For example, I'm not even sure whether the crash happens when app4
> already runs or when Make tries to spawn it; are you now saying that
> the former is the truth?
i think it can be both, basically the problem can occur any time after
the last app in the pipe piped app exits (because that's when the special
stack gets freed).
------------------------------------------------------------------------
now, let's see the real 'meat'. i apologize for having made you read
through all that above, but i did not want to leave it unanswered and
jump straight into the middle of the new information i found out.
1. the actors
ntvdm:
this is ther user mode component of windows that does all the emulation
needed by dos (v86), dosx (pm16/pm32), win16 (pm16) apps. it interfaces
to the kernel via ntdll (as any other subsystem does) and also has an
undocumented interface that can be used by the dos/dosx/win16 apps
(based on the debug symbols, the related internal functions are prefixed
by 'Bop', no idea what it stands for).
dosx:
this is the dos extender that sets up the protected mode environment for
dosx and win16 apps.
ntoskrnl:
the kernel, it plays one role (that we are concerned about): it is the
bridge between ntvdm and dosx in that whenever the 'Bop' interface is
used by dosx, a special code sequence will raise an invalid opcode
exception which ntoskrnl will recognize and will reflect the exception
back to ntvdm.
2. the play
i do not have a full understanding of how exactly the ntvdm subsystem works,
but the following should serve as a good enough guide to understand the
problem.
when a djgpp app is executed, eventually an ntvdm process is created in
which dosx will get executed and then the dpmi app(s), ie. we have quite
a few executables loaded into a ntvdm process's address space. one of
the things dosx does right at the beginning (ie. before executing the dpmi
app) is to allocate 4k of memory and create an LDT selector alias and
notify ntvdm that this memory range should be used as a special exception
stack (whenever needed, which is the case for reflecting down hardware
interrupts, etc).
any memory allocation from dosx and the dpmi apps goes through ntvdm (which
in turn calls ntdll/ntoskrnl). ntvdm maintains a double linked list of
descriptors of all allocated memory areas. a descriptor is a simple structure,
its fields describe the linear start address and size of the allocated range,
the LDT alias selector created for the range and most importantly to us, the
'owner' of the given range. 'ownership' here means the PSP selector of the
dpmi app that requested the memory allocation.
ntvdm has an internal variable called _CurrentPSPSelector which is used to
fill in the 'owner' field of this descriptor. _CurrentPSPSelector is updated
whenever a new dpmi app is created inside the given ntvdm process OR when
int 21/50 is called OR when a dpmi app exits (via int 21/4c normally). the
reason for that ntvdm maintains this ownership information is that whenever
a dpmi app exits, ntvdm will automatically free up all memory that the given
app allocated.
and therein lies the problem. what happens is that this special exception
stack is allocated by dosx while the _CurrentPSPSelector is 0 (ie. no dpmi
app has been executed yet). so far so good, after all dpmi apps will get
their unique (non-zero) PSP and therefore memory allocated by them should
never be confused by memory allocated by dosx. unfortunately, this is not
true because when a dpmi app exits, ntvdm will not only free up all the
memory allocated by the app but it will also reset _CurrentPSPSelector to
0. and should another dpmi app exit before a new one is executed, ntvdm
will happily free all the memory blocks whose owner is '0' - and there
goes our exception stack.
now you ask, when can such a situation occur? any time a dpmi app spawns
a child which spawns a grandchild (eg. 'make' executing 'sh' executing
'gcc'), when the grandchild exits, _CurrentPSPSelector is reset to 0
and if the child exits as well without spawning another grandchild, we
get the problem - exception stack is gone, any interrupt thereafter that
would get reflected back will cause the familiar page fault inside ntvdm.
in the 'make' example, if 'make' executes two children (ie. two separate
lines in the makefile), the second child will be run with damocle's sword
hanging on his neck.
3. the cure
the fundamental problem with ntvdm is that it maintains a simple static
variable describing the 'current dpmi app' instead of having a 'stack'
of PSPs of the dpmi apps as they are spawned from each other. or perhaps
it could use the parent PSP field in the just exited app's PSP to update
_CurrentPSPSelector (provided it is an LDT selector instead of some
real mode segment).
since ntvdm is beyond our control (or at least i think it is not worth
the hassle to create a patch for ntvdm.exe), we can try a workaround in
libc/dos/process/dosexec.c:direct_exec_tail (or whereever) where we could
set the PSP (via int 21/50) ourselves back to that of the parent after
the child returned (since the parent becomes again the 'current dpmi app').
so, that's all for now, i am looking for forward for the solution,
the owl
- Raw text -