Page 1 of 1

Issue in Task Linking with lcall and iret on x86

Posted: Tue Dec 06, 2011 2:46 am
by RajivKumarSrivastav
Can you please help me and let me know , why the "push ebp" instruction is corrupting the GDT entries ?.
Thing to note is that , some times it works and some times dont work.
Description with code is given below. I am sorry , its a big .
I am writing program for Task switching with Linking on x86 with help of FAR CALL (lcall) and IRET.
I have 3 TASKs ( T1,T2 & T3) with separate TSS and LDT for each task in GDT. I have also made kernel as TASK0 (i.e. T0) with its TSS0 and LDT0 in GDT table.
I have tried to give relevant code only to avoid confusion.
Kernel init code is given below.

Code: Select all

Kernel_init()
{
 setGDT();
 setIDT();
 Init_Timer(); // to 0x20 
 Sched_init();  // Will initialize the GDT with TSS and LDT for each task and load TR register too
 for(;;)
 {
    if(TimerFlag != 1){
      // Wait for First TimerInterrupt ;
      // Timer interrupt is used to change the priority
      continue;
    }
   Scheduler();
       
 }
}
TASK structure are given below:

Code: Select all

// file task.h 
struct TSS_STRUCT {
	uint32 back_link;
	uint32 esp0, ss0;
	uint32 esp1, ss1;
	uint32 esp2, ss2;
	uint32 cr3;
	uint32 eip;
	uint32 eflags;
	uint32 eax,ecx,edx,ebx;
	uint32 esp, ebp;
	uint32 esi, edi;
	uint32 es, cs, ss, ds, fs, gs;
	uint32 ldt;
	uint32 trace_bitmap;
};

/* structure of a task*/

struct TASK_STRUCT {
	struct TSS_STRUCT tss;
	uint64 tss_entry;        //descriptor of the TSS entry//
	uint64 ldt[2];
	uint64 ldt_entry;
	sint32 state;
	sint32 priority;
	ULONG  task_tss_sel;
	struct TASK_STRUCT *next;
};
Scheduler code will select the TASK based on priority and switch to it.

Code: Select all

// file task.c
struct TASK_STRUCT *current = &TASK0;   // initial setting

Scheduler()
{
    unsigned int sel[2], eflags;
    struct TASK_STRUCT *v = &TASK0, *tmp = 0;
    unsigned int cp;

     cp = current->priority;
      // This to select task based on priority
     for (; v; v = v->next) {
          if (((v->state==TS_RUNABLE) && (cp>v->priority))){
              tmp = v;
              cp = v->priority;
        }
       }
      if (tmp && (tmp != current)) {
         
         current->state = TS_RUNABLE;
         tmp->state = TS_RUNNING;
         current = tmp;
         sel[0]= 0;
         sel[1]= current->task_tss_sel;
         // Code to do FAR CALL
         __asm__ ("lcall %0": :"m" (*sel));
}
}
void do_task3 ()
{
   // Write to memory and return
   WritetoMemory( 0x3333);
   return;
}
Each task's TSS will have separate EIP to start the execution, which is set in Sched_init() function.
Below is code for which T3's EIP is linked in T3's TSS structure.

Code: Select all

// file - task.s 
task3_run:
                 call do_task3 //problem is here
                 iret
task3_end:  jmp task3_run
As mentioned in file task.s, when i step into "call do_task3" , i find the instruction as "push ebp and mov ebp, esp" which is done as we enter into function by compiler.
:( As soon as i execute the push ebp instruction i see that GDT table for T0,T1 and T2 are changed to initial value (assume T3 is running)which have TSS low and high address as 0x0.. GDT table entry for T3 is not changed and i see its BUSY with proper TSS struct address.
Can you please help me and let me know , why the "push ebp" instruction is corrupting the GDT entries ?

Code: Select all

// After "push ebp" one GDT entry will look like :
0x00c0890000000067ULL ;//  with TSS address as 0x0
// Before "push ebp", one  GDT entry will look like :
0xffc089fedae00067ULL; // where fffedae0 is TSS address 
Initial GDT values are :

Code: Select all

uint64 kgdt[MAX_GDT_ENTRIES] = {
  0x0000000000000000ULL , // null -0
  0x00cf9b000000FFFFULL, // cs -
  0x00cf93000000ffffULL, // ds and ss -2

  0x00c0890000000067ULL,  // tss0 // 0x18 
  0x00c082000000000fULL , //ldt 0  //0x20 

  0x00c0890000000067ULL,  // tss1   // 0x28 
  0x00c082000000000fULL, // ldt1   // 0x30 

  0x00c0890000000067ULL, // tss2   // 0x38 
  0x00c082000000000fULL,  // ldt2   // 0x40 

  0x00c0890000000067ULL,  // tss3   // 0x48 
  0x00c082000000000fULL, // ldt3   // 0x50 
};

Re: Issue in Task Linking with lcall and iret on x86

Posted: Tue Dec 06, 2011 3:01 am
by Brendan
rajiv123 wrote:I am writing program for Task switching with Linking on x86 with help of FAR CALL (lcall) and IRET.
Don't use "far call" for task switching. Instead use "far jmp".

For "far call" the CPU leaves the old task marked as "busy" and assumes that you will return ("far ret") back to it. You're not doing that (and shouldn't be doing that) in your scheduler.

For "far jmp" the CPU changes the old task to "not busy anymore" and doesn't assumes that you will return ("far ret") back to it. This is what you want.

Using "far jmp" for hardware task switching can be a little confusing at first. For an example, think of this code:

Code: Select all

 for(;;) {
    "far jmp <some other task>"
    foo();
}
In this case it looks like the "far jmp" will break out of the loop and cause all sorts of problems, but it won't - "foo()" will be executed (and the loop will continue) after the someone does a "far jmp" back to this task.


Cheers,

Brendan

Re: Issue in Task Linking with lcall and iret on x86

Posted: Tue Dec 06, 2011 3:33 am
by RajivKumarSrivastav
Thanks Brendan for the reply.
I need the task linking. The reason is T1,T2 and T3 are small task. And if one of them finish, it should return back to for(;;) loop of Kernel_init and select next task to run.
I have made Kernel_init as TASK0, and will not do task switch if Scheduler selects TASK0 to switch.
The idea is that , if kernel is not executiong any interrupt, then we will be in Kernel_init for(;;) loop, and during this time, i want to run T1 ,T2 and T3. And if T1,T2 or T3 are
interrupted , we can call ISR and come back to T1,T2 or T3 with help of saved TSS again.


In file task.s , i am doing IRET to switch back to old task ( caller task , which in this case is always T0).
And i am not getting , why do_task3 is corrupting TSS entry of GDT.
Sorry for small mistake in Scheduler code. It should be added with && (tmp != TASK0))

Code: Select all

Scheduler()
{
    unsigned int sel[2], eflags;
    struct TASK_STRUCT *v = &TASK0, *tmp = 0;
    unsigned int cp;

     cp = current->priority;
      // This to select task based on priority
     for (; v; v = v->next) {
          if (((v->state==TS_RUNABLE) && (cp>v->priority))){
              tmp = v;
              cp = v->priority;
        }
       }
      if (tmp && (tmp != current) && (tmp != TASK0)) {
         
         current->state = TS_RUNABLE;
         tmp->state = TS_RUNNING;
         current = tmp;
         sel[0]= 0;
         sel[1]= current->task_tss_sel;
         // Code to do FAR CALL
         __asm__ ("lcall %0": :"m" (*sel));
}
}

Re: Issue in Task Linking with lcall and iret on x86

Posted: Tue Dec 06, 2011 3:37 am
by RajivKumarSrivastav
I can give a try with LONG JUMP and IRET also later point of time in my code, but trying to solve this issue first.

Re: Issue in Task Linking with lcall and iret on x86

Posted: Fri May 25, 2012 5:53 am
by RajivKumarSrivastav
Thanks everyone for reply. I got the answer after so many trials.
There was a problem of overlap between CODE & DATA section ( though i had written linker to start DATA just after CODE).
So finally, when i give more space to CODE section, GDT Table corruption does not happen.