I can see that, somehow, Jazz Jackrabbit crashes when configured for Sound Blaster and using a Sound Blaster 2.0 card on UniPCemu(DSP reporting as version "2.o1". DSP Version "2.00" (older UniPCemu builds) before that change don't crash it).
When using said card instead of a 1.x card, it crashes with a stack fault pretty much immediately after starting the DMA for the Sound Blaster audio?
Anyone knows anything about this?
Edit: It's probably a problem with the Sound Blaster 2.0 or it's driver that's causing the stack fault? Since the only thing the hardware changes with the newer version(2.0 vs 1.x) is the new commands and it's version number(2.01 vs 1.05), perhaps the issue has something to do with that?
The last thing I see it do is starting up DMA autoinit 8-bit PCM playback(don't know if it's high speed or not) just before crashing on SB2.0?
Hi! Not sure if this helpful, but I used to play Jazz Jackrabbit with a Pro AudioSpectrum 16.
As you know, it also incorporates a ThunderBoard chipset, which is roughly SB 1.5 compatible.
Again not sure if this is helpful. Just wanted to note that actual SB 2.0 compatibility is perhaps not needed, even.
At Mobygames, there's a photography of a Jazz game box. it just says "Jazz Jackrabbit supports: Sound Blaster" https://www.mobygames.com/game/dos/jazz-jackr … CoverId,134302/
"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel
Well, that very first DMA transaction is weird as well(at least on the SB side, haven't checked the DMA chip yet): the DMA block size(which was set immediately before that) is set to 0, thus only 1 byte(sample) of audio before issuing it's interrupt? Is that supposed to happen normally?
OK. This is a list of written bytes:
0xE1(Version command). Gives 2.1(firstbyte.secondbyte).
0xD1(Enable speaker).
0xE1(Version command). See first call.
0xD1(Enable speaker).
0x40(Set time constant).
0xAE(Set time constant parameter: 12195Hz)
0x1C(Auto-initialize DMA DAC). This uses a block size of 0(reset default)? Thus 1 sample.
DMA sample: 0x80. Raises an IRQ. Autoinit of SB2.0 resets for 1 sample to render. Starts playback for said sample on the timing that's setup(when the timer ticks again for rendering the buffered sample).
Then I see it raising interrupts for each rendered sample, with them being acnowledged by a read from the DSP's data register.
Edit: Just have been making some little notes about the DREQ bitflags(a simple byte value containing the status of DMA playback to use), which is mainly used for setting and clearing DREQ(It's the SOUNDBLASTER.DREQ variable):
1Flag bits(hex bit values(OR'ed together)): 2x1=Transfer running 3x2=Acnowledged, waiting for playback to happen. Set by SB DMA autoinit and samples left to render when sending a sample to render. 4x4=Acnowledged DREQ. Cleared by timing of a sample(both silent and from the rendering buffer). Set by DACK being raised by the DMA controller. 5x8=Waiting for IRQ to be acnowledged to resume DMA playback. Set by SB DMA executing autoinit when raising an IRQ. Cleared when acnowledging an raised IRQ that was pending. 6x10=Recording flag. Ignored for DMA purposes. 7 8Bit values 1 and x10(only when recording) are set when starting a DMA playback operation. 9 10The DMADisabled flag 11 12The DREQ signal for the DMA controller is raised(set high) when all bits are cleared but bit 0. Bit 4 is ignored for DMA DREQ determination and DMA isn't disabled by the DMADisabled flag(when it's zeroed). 13 14DREQ is fully cleared when DMA finishes, DMA command is given(before completing it), Direct ADC command is given, Set DMA Block Size command is given, Direct DAC command is completed, DMA DAC finishes(without autoinit) it's samples(programmed count or DMA block count), DAC is silenced, DMA ADC finished without autoinit, and finally when the DSP is reset. 15 16DMADisabled is: 17- Initialized to zero. 18- Set by silencing samples raising an IRQ on completion. 19- Cleared when starting a new DMA transfer. 20- Set by command D0(Halt DMA operation, 8-bit) when DREQ isn't zero. 21- Cleared by command D4(Continue DMA operation, 8-bit) when set. 22- Cleared by command 10(Direct DAC).
One interesting thing: when trying to restart jazz.exe when it's crashed with exception 0xC, it complains that it can't find setup.exe? CS is 0x20 when it crashes, with it pointing to a memory location above 1MB? SS is seemingly 16-bit, with the PUSHA(D) instruction crashing on memory location of (E)SP=FFFE? The full ESP value that's being used is 0xFFFFFFFF, but it might only be using the lower 16-bits in 16-bit mode?
Edit: It's a PUSHAD at 0020:132F. CS=Base:101000,limit:ffff. SS=Base:14C50,limit:ffff.EFLAGS=3002.
So it's probably during some interrupt handling this happens(since EFLAGS.IF=0)?
OK. Found out some stuff about the memory manager of Jazz Jackrabbit, as it's crashing:
TR(0158) is the current task. TR+8=Current task as a data segment, TR+0x10=TR's LDT. Probably TR+0x18=TR's LDT as a data segment?
Then, I found out that DS:0401 bit 4 seems to be some bit determining 32-bittyness, making the return to the caller or something like that(at 0020:13eb) jump to a 32-bit PUSHAD/POPAD (POPAD in normal POPs and SUB for SP?) or a plain 16-bit POPA instruction?
I see it setting the IOPL on the stack, as well as inspecting the current task and modifying it's SP value using the TR+8 descriptor(the current task's data segment descriptor).
Edit: OK. Strange: I see it setting the SP0 in the task to FFFFh at 0020:00001363.
Hmmm... I then see it matching some descriptor(B3) to some memory value and the interrupt flag being set on the stack(&0x200), then jumping to a place it hadn't been before: 0020:00003e80. So, it's found out the original task(?) is a user-mode task, then takes some special action based on that... Hmmm...
Edit: It then clears bit 2 of byte DS:[403], which it was looking at for something earlier...
Edit: Then checks something for user mode again(417h)?
Edit: Actually, non-user mode is non-CPL0 instead(so CPL of 1,2 or 3 is counted the same). So it's checking against the kernel privilege(CPL 0) itself and goes on based on that.
I now end up at 0020:00003f44.
Edit: Oooohhh.... I see something interesting: The jump based on the difference of the start of the IRQ handler eventually takes a different path: the difference is 0xC(thus jumping to 0020:00000cbc). Since the difference is 0xC, that means 12 bytes are pushed on the stack(EIP(at BP),CS(at BP+4),EFLAGS(at BP+8)) and thus an interrupt/exception from the kernel level, setting ESP from FF8h to FECh(the base address of the interrupt handler's 'parameters'), so it's not an exception handler, but instead a interrupt call to whatever interrupt handler is running?
Edit: It gets to 0020:00003d23 because it's not a 0xD fault handler(it's an kernel privileged interrupt call after all).
Edit: Hmmm... Interrupt 0xD is for MS-DOS the IRQ5 handler, so it's probably an interrupt arriving from the Sound Blaster 2.0?
Edit: Then it checks against interrupt 0x34, which doesn't match, so it jumps to 0020:35D0.
Edit: It loads TSS+68h into AX, so that's a lot after the 80286 TSS's end(0x26BC), then 2 bytes after that to DX(0x417). Perhaps some kind of pointer(417:26BC)?
Edit: It loads 5B into ES at 0020:00003663...
Edit: OK. So it is overwriting at B3:0002 with EFLAGS=0x3202, so running with interrupts(as it should when handling a hardware interrupt, in this case the Sound Blaster IRQ, seeing from the start of the 0x20:xxxx handler).
Edit: I see it copying said block of memory(B3:0002 as CS:IP and EFLAGS=0x3202) over the return address?
Edit: Eventually, there's an IRET to the IRQ handler(?), which pops 26BCh(IP), 0417h(CS), 3000h(EFLAGS), 0ffah(SP), 005bh(SS). So it returns to said PL3 IRQ5 interrupt handler?
It eventually reaches an interrupt handler at 0020:0C7a again?
Edit: ESP is FAA at both the IRET and when returning to the handler at 0020:0C7A, so it's handling a new event or interrupt nested within the old interrupt handler of PL3(probably the one launched the previous IRET)?
Edit: I see ESP keep being the same between IRET and the start of the 0xD handler, while it keeps adding 2 more bytes(error code) ever other interrupt?
Edit: Said exception at segment 417h seems to be the origin of the stack overflow? Seeing as it's calling seems to cause the 2-byte decrement in ESP when the GP fault handler starts, thus pushing an error code for some error to be handled?
Edit: Making a log of segment 417h I see it executing IRET to segment B3 lots of times, with EBP keeps decreasing until it's really low and the kernel thread crashes. So the next step would be segment B3 instead...
Edit: OK. Segment B3 just seems to execute only one instruction: HLT at 00B3:0002, which causes a #GP fault to kernel mode, causing the stack to behave the way it does(since CPL!=0)?
Edit: Looking more into the B3 segment, I also see a RETF to 008F:0470 mixed in various times?
Edit: And 8F just leads to itself and segment E7 during RETF and B3 during JMP and CALLF.
Hmm... One thing is interesting: Jazz Jackrabbit's SB2.0's GP fault handler keeps recursively faulting on INTDh handlers? The IRQ from each SB2.0 sample starts interrupt 0xD without parameters, which it adds some information to and returns to user mode with a lowered EBP, effectively allocating a stack frame for handling the interrupt in user mode. Then it IRET's back to user mode.
Eventually, user mode ends up at a HLT instruction, which causes a #GP fault to kernel mode, adding 2 bytes of the error code on top of the CS,EIP,FLAGS pushed on the stack(so it's difference isn't 0xC anymore, but 0xE instead). Then said fault handler moves ESP to EBP and the kernel's SP0 register in it's TSS, causing future fault handlers to fault with their return information(from the HLT instruction) to be stored below that. Said process is somehow never undone, so when it returns, executes user-mode code, it eventually still faults back to the kernel to handle said fault, after which it repeats the saving of the stack, which causes the user-mode HLT to repeatedly bump the stack further down, until the kernel reaches a point of storing it's user-mode registers with pusha which it can't(the stack being filled with infinitely repeated, never unwound stack frames) after which the ugly 0xC fault to the MS-DOS prompt shows itself and terminates the program, corrupting MS-DOS to the corrupted MS-DOS prompt?
Hmmm... I see ES:[0002](which is the SP0 field in the TSS), where ES~TR(but a data segment by adding 8 to the selector) being written with a new value, but is that actually being used for the exception that follows for the HLT?
Edit: Hmmm... It loads F98h there(at 10a5f2), while the exception ends up from f8e to f8c?
Edit: It's setting SS0 to BP+44h, which seems to be constant?
Edit: Fault handling seems to be correct itself, but why does it keep decreasing BP infinitely instead of returning SS0 to it's base level(ffch?)?
OK. Just looked at the instruction flow after that. It sets up a stack frame, then checks BP+6 for bit 1 to be set or not. But at that location(with a normal interrupt) would have FLAGS? That would make no sense at all?
But, what if the interrupt had an error code? Then it WOULD make sense:
Normal interrupt stack:
1ff8: IRET stack top! Starts being OOR! 2ff6 iret ss 3ff4 iret sp 4ff2 iret flags=3283 5ff0 iret cs 6fee iret ip 7fec old bp=3ab0 <- bp 8fea es 9fe8 ax
Exception stack with fault code:
1ffa: IRET stack top! Starts being OOR! 2ff8 iret ss 3ff6 iret sp 4ff4 iret flags 5ff2 iret cs=3283(checked for bit 1 being set) 6ff0 iret ip 7fee error code 8fec old bp=3ab0 <- bp 9fea es 10fe8 ax
So now we know that the issue is with some exception handler instead of a normal interrupt! It's the handling at an (probably loading of some segment) at segment 3283 for privilege level 3! Probably one of those #NP fault handlers!
Edit: The current stack top is at ff8(evidenced by the reading of the TSS check of SS0 for the ffffh value), so something's expecting an error code, where there isn't one for some weird reason?
Edit: It's the handler at 0020:0B52.
Edit: It's the interrupt 8h handler?
Ooooohhhh.... Interesting:
Selecting the "Sound Blaster Clone" option in the settings makes it run without crashing on starting on the Sound Blaster 2.0 configuration?
OK. Having started up the game on the "Sound Blaster clone" setting instead of plain "Sound Blaster" for the emulated SB 2.0 card, I notice the following weirdness when trying the bonus level from the main menu:
Pressing forward has no visible effect? Backward, left and right on the analog stick work as expected?
I see that starting the auto-init DMA commands on SB 2.0 don't act any different than their non-auto-init versions: they still start out with the block size of the two parameter bytes? It's just that it's reloading it from the SB 2.0's Set DMA Block size command when that buffer and any buffers after it is processed(all those bytes have been transferred)?
So, Jazz Jackrabbit would send 1c,lo,hi for the DMA transfer to transfer lo+(hi*256) bytes, after which it would start to load the default(since the Set DMA Block size command size isn't received after reset) size instead? And that default size being 0, thus each DMA sample=1 interrupt, but only after said param 16-bit amount of bytes?
Would that be correct behaviour?
Although I see sblaster.cpp reading the 2 bytes in the command buffer while it's parameter table contains 0 parameters for the DMA auto-init commands, thus undefined behaviour?
So segment 417h is at least containing the Sound Blaster code?
It does send those two parameter bytes to the SB 2.0 and up? Are those just undefined implemented on SB 2.0 only? Or is it a bug only? How does the Set DMA Block size setting affect all this? Does it only affect auto-init after the command 0x1C parameters are sent? Or does 0x1C ignore those parameters, having no DMA block size(still set to 0x0000 at that point in UniPCemu), causing it t0 throw an IRQ each sample?
OK. It might never be able to return, because I see it what seems to be the IRQ5 handler(for the Sound Blaster) keeping to trigger each sample, which adds 0xAE(the sample rate constant) to ds:[26ba]?
Could it be that it expects the sample rate constant to be expired before triggering the IRQ? So perhaps UniPCemu's time constant isn't correct?
UniPCemu currently converts the constant into a rate, then starts triggering said a rate on the current timing it's using(resetting the timer to a different rate when setting the time constant)...
I've now changed said timer to run at 1MHz, then simply divide it up by the current count(just like the 8254 PIT does) to obtain a basic sample rate, then time the running DMA transfers using that timing when the counter ticks from 1 to 0(reloading happens when writing/reading a DMA sample to/from the DMA controller and when a new transfer is started), starting at 256-[programmed rate].
OK. Just tried Jazz Jackrabbit with the same IRQ/DMA settings on Dosbox 0.74-3(the most recent one from Dosbox.com). When trying the same settings on the SB 2.0 with the normal Sound Blaster setting, it doesn't crash like UniPCemu does, but it runs without sound.
When trying the same with setting it up for the "Sound Blaster compatible (stereo)"-named setting(same IRQ and DMA settings using manual settings), it runs without issues on Dosbox's SB 2.0, just like UniPCemu does?
One thing I'm thinking about: how does the DSP busy bit on port work? When is it set or reset?
Also, I notice one thing while selecting the Sound Blaster compatible setting in the setup: the information bar in Jazz Jackrabbit's (with it's HP, timing etc.) seems to be gone? As in, only the gameplay part is still there(so no status etc. is visible)? All I did was play the bonus levels(first, second partly, then returned to the main menu) first.
Edit: Perhaps an ET4000 issue?
Edit: Nope. It's the pure VGA that's being emulated. So a VGA issue somehow? The screen is only partly rendered?
Edit: OK. Comparing to Dosbox for the first level, it doesn't look like it's incorrectly rendered, but it's just more than the display can handle somehow? Like it's cut off just after the first scanline of the status bar?
Edit: OK. The issue was the splitscreen operation, which was determining that the split screen meant that it should render on the other monitor instead of the current one(just affecting VRAM fetches).
Edit: Having fixed that, I notice that the vertical display end register indicates active display is only 199 pixels high? Shouldn't that need to be 200 usually?
OK. In the normal "Sound Blaster" setting it, upon reaching 0417:2308 after 0417:2307, seems to somehow be executing something which looks like a strange interrupt of some kind?
Edit: It seems to be located at position 68h in the IDT, so it's interrupt Dh, thus a #GP fault or Sound Blaster interrupt?
Edit: Huh? I see it starting to execute a RET (opcode C3) at that location, but while it's starting to wait for it's common POP timing, the instruction aborts(as it should, to handle the common timing before a POP instruction actually starts popping it's data requesting it from the BIU to be read) and starts fetching and executing a new instruction when triggering the next CPU cycle, instead of continuing the executing instruction???
Edit: OK. More weirdness: the debugger was visible(thus the emulation should be halted until pressing the cross/NUM2 key), but the emulation somehow continued executing. That's not something that's supposed to happen, as the emulation shouldn't be running at that point(just the debugger thread) 😖
Edit: Of course, on my second try, I see it triggering an IRQ at 0020:C7A on that location, from a real Sound Blaster IRQ(hardware interrupt 0xD)! The SB timer has counted down from 0xAE(+1) to 0x51 in that time(those few instructions).
Edit: That's probably just bad timing!
Edit: I see it executing, returning to 0x25EB.
Edit: And, immediately on the next instruction interrupt 0x8(IRQ0) happens, after which it crashes once again.
Edit: Hmmmm.... Are interrupts supposed to be enabled while handling said code?
Edit: OK. There might be a possible issue with detecting busy instructions/interrupts/TSS-based multitasking... Although I can't exactly remember why executing instructions state aren't counted as a 'busy execution phase', since the instruction is clearly still running when that's the case.
Edit: Fixed said 'bug' (if that was the whole story) wasn't enough. It's still crashing due to a stack overflow before/at reaching said location, never returning from the function. That's at/directly after it's RETF instruction, because it's handling a PIT interrupt there(IRQ0).
Edit: Just added multiple breakpoint support(up to 5 breakpoints that trigger together(basically OR'ed together in their usage)). So they're handled like they're one condition, and if any one is matched, the breakpoint is active for the current instruction.
Managed to make a log with segment 20h and 417h only. That should at least tell us what's happening to the strange interrupt 8h that's occurring and causing Jazz Jackrabbit to crash?
I think it might have something to do with the strange stack handling of the interrupt handler? I remember that the interrupt handler decreases ESP normally, building saved data of the originating task, but eventually overwrites the top of the stack with new data instead of doing it somewhere lower on the stack(on the EBP bottom of the stack, for instance, which would make more sense(if you're not wanting to generate an infinite loop with the last interrupt returned to))?
Edit: OK, at 0020:00000cbf it gets interesting. It has determined that it's an interrupt(probably, since the difference of the SS0 and BP was 0xC), so it's starting the interrupt handling here?
ESP is returned to pointing to the original stack frame of the IRQ being triggered at that point(at FEEh).
Edit: OK. At some point, the build stack is skipped back, while a return to the call handler that had (0xD,0x0,IRET frame) as parameters is done from the bottom of the stack(it's returning to the call IP value at 0020:00001383).
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h <- bp <- sp 28 29 30Execution at 0020:0cbc=Start of IRQ/interrupt handler 3198:63E=BP(fb4). 32JMP to fe8 address(=cc6h) is done with this stack at 0020:00001383. 33It reaches 0020:00003d09 when returning from this(after a unconditional JMP). 34It compares interrupt number(0xD) against 0x75. Then starts adding more to the stack.
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) 28fb2: ax=1ch 29fb0: dx=Ah 30fae: FEA's contents=Dh 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h <- bp (end result after stack build for next check of 34h) 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0 <- sp (end result after stack build for next check of 34h) 36 37OK, it then checks FAEh against 34h, another interrupt check.
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) 28fb2: ax=1ch 29fb0: dx=Ah 30fae: FEA's contents=Dh 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0 <- sp (end result after stack build for next check of 34h) 36 37OK, it then checks FAEh against 34h, another interrupt check, at 0020:000035a8.
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) 28fb2: ax=1ch 29fb0: dx=Ah 30fae: FEA's contents=Dh 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0 <- sp (end result after stack build for next check of 34h). 36 37It then prepares something for some kind of TSS transfer?
Edit: Apparently, the TSS location 34h and up contains some sort of table with interrupt vectors(the IVT for protected mode? It looks like they're stored like a normal IVT, but with protected mode 16-bit segment:offset instead?).
Handling seems to start at 0020:000035db.
It eventually seems to return to 0020:00003d39.
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 28fb2: ax=1ch 29fb0: dx=Ah 30fae: FEA's contents=Dh <- sp returned here at 0020:3d39. 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0
AX=IP, DX=CS for the retrieved interrupt vector at this point.
It discards FAEh's stack entry and checks the returned DX(=CS for the interrupt vector)?
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 28fb2: ax=1ch 29fb0: dx=Ah <- sp when checking DX against 0. 30fae: FEA's contents=Dh 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0
OK. It then loads some data from RAM(B3:0002?), then proceeds to push the interrupted location's FLAGS on the stack, at FB2h.
OK. It then loads some data from RAM(B3:0002?), then proceeds to push the interrupted location's FLAGS on the stack, at FB2h.
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 28fb2: IRET flags=3202h <- sp after pushing FLAGS. 29fb0: dx=Ah 30fae: FEA's contents=Dh 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0
Then BX is added as well(B3), and CX as well(02):
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 28fb2: IRET flags=3202h 29fb0: bx=B3h 30fae: cx=2h <- sp after pushing CX. 31fac: 3d36's return IP=3D39h 32faa: bp=fb4h 33fa8: es=160h 34fa6: di=Ah 35fa4: bx=0
Then, a constant 3h, followed by 0h, 1h, dx(417), ax(26bc):
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happens here) <- bp (end result after stack build for next check of 34h, at 0020:000035a5) 28fb2: IRET flags=3202h 29fb0: bx=B3h 30fae: cx=2h 31fac: 3h 32faa: 0h 33fa8: 1h 34fa6: dx=417h 35fa4: ax=26BCh <- sp after pushing AX at the end.
This is followed by a call, then enter 0,0:
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happened here) 28fb2: IRET flags=3202h 29fb0: bx=B3h 30fae: cx=2h 31fac: 3h 32faa: 0h 33fa8: 1h 34fa6: dx=417h 35fa4: ax=26BCh 36fa2: ip=3D64h 37fa0: bp=FB4 <- sp <- bp
It then reached 0020:00003636. So far, everything seems sane.
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happened here) 28fb2: IRET flags=3202h 29fb0: bx=B3h 30fae: cx=2h 31fac: 3h 32faa: 0h 33fa8: 1h 34fa6: dx=417h 35fa4: ax=26BCh 36fa2: ip=3D64h 37fa0: bp=FB4 <- bp 38f9e: bp=fa0h <- sp
It then prepares for something at 0020:00003660. It sets 98:403 bit 2 to 1. It then pops bp to return to the previous stack frame?
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happened here) 28fb2: IRET flags=3202h 29fb0: bx=B3h 30fae: cx=2h 31fac: 3h 32faa: 0h 33fa8: 1h 34fa6: dx=417h 35fa4: ax=26BCh 36fa2: ip=3D64h 37fa0: bp=FB4<- sp <- bp
It loads CX from BP+C(=3), points DS to the stack, loads SI from BP+Eh(2), AX with that CX(=3), multiplied by 2(=6), compares AX(=6) against DI(=1000h)...
Substracts AX from DI(1000-6=FFA)? So ES:DI now points to 2 bytes above the IRET stack frame of the interrupted procedure?
It moves said address to BX, adds AX(6) to SI(FAE), thus SI=FB4(the previous EBP). It pushes SI...
1ff8: sp0 base for the original IRQ0 handler to return to the never returning code 2ff6: IRET ss 3ff4: IRET sp 4ff2: IRET flags 5ff0: IRET cs 6fee: IRET ip (base frame for IRQ0) 7fec: 0h 8fea: Dh 9fe8: call IP=cc6h 10fe6: ??? 11fe4: ??? 12fe2: ??? 13fe0: ??? 14fde: gs 15ffc: fs 16fda: ds 17fd8: es 18fd6: 0h 19fd2: eax=1Ch 20fce: ecx=0 21fca: edx=Ah 22fc6: ebx=0 23fc2: esp=FD6h 24fbe: ebp=3EB4 25fba: esi=2127h 26fb6: edi=2174h 27fb4: [ds:63E]=fb4h (check against 75h happened here) 28fb2: IRET flags=3202h 29fb0: bx=B3h 30fae: cx=2h 31fac: 3h 32faa: 0h 33fa8: 1h 34fa6: dx=417h 35fa4: ax=26BCh 36fa2: ip=3D64h 37fa0: bp=FB4 <- bp 38f9e: si <- sp
It then substracts AX from SI, returning it to FAEh. It then clears the direction flag and copies from FAE to FFA, just overwriting the IRET IP,CS,EFLAGS with B3:0002, flags=3202h.
So what it's effectively done at this point is replace the IRET information with it's interrupt vector, having what seems to be the original CS:IP of the interrupted procedure(which never regains control) at [FA6]:[FA4].
Edit: OK. This doesn't happen on the kernel's stack, but on the top of the user's stack instead?
Last edited by superfury on 2020-02-16, 17:59. Edited 1 time in total.