Hi, I am in 64 bit mode long, QWORDS/UINT64 only for me, but I hope this helps you out.
I have several methods for switching processors in my OS.
One being an Interrupt.
I have built a custom Interrupt handler for each CPU, this builds the Interrupt structure I require so it is always the same and I can modify it as I wish. Code Below.
Only showing a little of what it does, but this should be complete enough for this reply.
Code: Select all
FIL void tCPU::WriteInterruptCode(BYTE Interrupt, InterruptHandlerProc* HandlerAddress, QWORD AttachConditions)
{
BYTE* b = this->Interrupt[Interrupt].Code;
// cli - Disable Interrupts
*b = 0xfa; b += sizeof(BYTE);
if ((AttachConditions & tAttachConditions::acPushGPRsPassStackAddressCallHandler) == tAttachConditions::acPushGPRsPassStackAddressCallHandler)
{
// push rax
*b = 0x50; b += sizeof(BYTE);
// push rbx
*b = 0x53; b += sizeof(BYTE);
// push rcx
*b = 0x51; b += sizeof(BYTE);
// push rdx
*b = 0x52; b += sizeof(BYTE);
// push rsi
*b = 0x56; b += sizeof(BYTE);
// push rdi
*b = 0x57; b += sizeof(BYTE);
// push rbp
*b = 0x55; b += sizeof(BYTE);
// push r8
*b = 0x41; b += sizeof(BYTE);
*b = 0x50; b += sizeof(BYTE);
// push r9
*b = 0x41; b += sizeof(BYTE);
*b = 0x51; b += sizeof(BYTE);
// push r10
*b = 0x41; b += sizeof(BYTE);
*b = 0x52; b += sizeof(BYTE);
// push r11
*b = 0x41; b += sizeof(BYTE);
*b = 0x53; b += sizeof(BYTE);
// push r12
*b = 0x41; b += sizeof(BYTE);
*b = 0x54; b += sizeof(BYTE);
// push r13
*b = 0x41; b += sizeof(BYTE);
*b = 0x55; b += sizeof(BYTE);
// push r14
*b = 0x41; b += sizeof(BYTE);
*b = 0x56; b += sizeof(BYTE);
// push r15
*b = 0x41; b += sizeof(BYTE);
*b = 0x57; b += sizeof(BYTE);
// movabs rax, HandlerAddress
*b = 0x48; b += sizeof(BYTE);
*b = 0xB8; b += sizeof(BYTE);
*(QWORD*)b = (QWORD)HandlerAddress; b += sizeof(QWORD);
// mov rdi, rsp
*b = 0x48; b += sizeof(BYTE);
*b = 0x89; b += sizeof(BYTE);
*b = 0xe7; b += sizeof(BYTE);
// call rax
*b = 0xFF; b += sizeof(BYTE);
*b = 0xD0; b += sizeof(BYTE);
// movabs rax, LocalAPICAddress + 0xB0
*b = 0x48; b += sizeof(BYTE);
*b = 0xB8; b += sizeof(BYTE);
*(QWORD*)b = (QWORD)LocalAPICAddress + 0xB0; b += sizeof(QWORD);
// pop r15
*b = 0x41; b += sizeof(BYTE);
*b = 0x5f; b += sizeof(BYTE);
// pop r14
*b = 0x41; b += sizeof(BYTE);
*b = 0x5e; b += sizeof(BYTE);
// pop r13
*b = 0x41; b += sizeof(BYTE);
*b = 0x5d; b += sizeof(BYTE);
// pop r12
*b = 0x41; b += sizeof(BYTE);
*b = 0x5c; b += sizeof(BYTE);
// pop r11
*b = 0x41; b += sizeof(BYTE);
*b = 0x5b; b += sizeof(BYTE);
// pop r10
*b = 0x41; b += sizeof(BYTE);
*b = 0x5a; b += sizeof(BYTE);
// pop r9
*b = 0x41; b += sizeof(BYTE);
*b = 0x59; b += sizeof(BYTE);
// pop r8
*b = 0x41; b += sizeof(BYTE);
*b = 0x58; b += sizeof(BYTE);
// pop rbp
*b = 0x5d; b += sizeof(BYTE);
// pop rdi
*b = 0x5f; b += sizeof(BYTE);
// mov DWORD PTR [rax], 0x0
*b = 0xc7; b += sizeof(BYTE);
*b = 0x00; b += sizeof(BYTE);
*(DWORD*)b = 0; b += sizeof(DWORD);
// pop rsi
*b = 0x5e; b += sizeof(BYTE);
// pop rdx
*b = 0x5a; b += sizeof(BYTE);
// pop rcx
*b = 0x59; b += sizeof(BYTE);
// pop rbx
*b = 0x5B; b += sizeof(BYTE);
// pop rax
*b = 0x58; b += sizeof(BYTE);
// iretq
*b = 0x48; b += sizeof(BYTE);
*b = 0xCF;
return;
}
... Some of my other stuff, which is not needed to be shown here
This is called like this.
Code: Select all
FIL void tCPU::AttachInterrupt(BYTE Interrupt, const char* InterruptName, InterruptHandlerProc* HandlerAddress, QWORD AttachConditions)
{
this->Interrupt[Interrupt].InterruptName = InterruptName;
WriteInterruptCode(Interrupt, HandlerAddress, AttachConditions);
ChangeInterruptPointerAddress(Interrupt);
}
, and you will need this code for complete-ness.
Code: Select all
FIL void tCPU::ChangeInterruptPointerAddress_Direct(BYTE Interrupt, BYTE* Address)
{
QWORD IDT = (QWORD)&this->IDTE[Interrupt];
*(volatile WORD*)(IDT + 0) = ((QWORD)Address >> 0) & 0xFFFF;
*(volatile WORD*)(IDT + 6) = ((QWORD)Address >> 16) & 0xFFFF;
*(volatile DWORD*)(IDT + 8) = ((QWORD)Address >> 32) & 0xFFFFFFFF;
}
FIL void tCPU::ChangeInterruptPointerAddress(BYTE Interrupt)
{
ChangeInterruptPointerAddress_Direct(Interrupt, this->Interrupt[Interrupt].Code);
}
So... what does the above do....
When an Interrupt happens it calls the code created by "WriteInterruptCode", IDT is setup with "ChangeInterruptPointerAddress_Direct". Note Interrupts are disabled before I call these, Not shown!
The inserted code from the function "WriteInterruptCode" pushes all the registers on the stack and sets up RDI = RSP.
This code then calls my c++ code, which looks like this.
Code: Select all
void Kernel::ProcessAPICTimer_CPU0() { register QWORD* InterruptPushStack asm("rdi"); ABIT.System.OS_GenericOS.CPU[ 0]->ProcessAPICTimer(InterruptPushStack, ABIT.CPUs.CPU[ 0]); }
Basically rdi is passed to this function as parameter and passed to my master function.
Using this method I do not need to find out what CPU was interrupted and I know where the stack-top was with all the registers.
So I can use a structure like this.
Code: Select all
union Packed() tGPR
{
struct Packed()
{
QWORD gpr[20];
};
struct Packed()
{
QWORD r15;
QWORD r14;
QWORD r13;
QWORD r12;
QWORD r11;
QWORD r10;
QWORD r9;
QWORD r8;
QWORD rbp;
QWORD rdi;
QWORD rsi;
QWORD rdx;
QWORD rcx;
QWORD rbx;
QWORD rax;
QWORD rip;
QWORD CS;
QWORD RFLAGS;
QWORD rsp;
QWORD SS;
};
};
I am then free to modify the registers, copy the registers, read the registers, save the RSP, or what ever I ike to do.
One example is I check to make sure an interrupt was called from user space by doing this.
Code: Select all
// Only call if interrupted from Userspace, check RIP
if (*(InterruptPushStack + 15) >= Util.GetVirtualAddress_4k(Userspace_Start_PML4E, 0, 0, 0))
{
__SYSCALL_CheckAccountFromInterrupt(InterruptPushStack, CPU, (Kernel::tSystem::tOS_GenericOS::tCPU::tIPAccounts::tAccount*)CPU->Generic_CurrentAccountWithSomethingToDo);
}
.
I could have casted, but I used "InterruptPushStack + 15" instead.
It really is endless once you get into it, good luck..