What does the ljmp instruction do in a Linux kernel system call?

I am studying the linux kernel source (old version 0.11v). When I checked the fork system call, there is some asm code to switch the context as follows:

/* * switch_to(n) should switch tasks to task nr n, first * checking that n isn't the current task, in which case it does nothing. * This also clears the TS-flag if the task we switched to has used * tha math co-processor latest. */ #define switch_to(n) {\ struct {long a,b;} __tmp; \ __asm__("cmpl %%ecx,current\n\t" \ "je 1f\n\t" \ "movw %%dx,%1\n\t" \ "xchgl %%ecx,current\n\t" \ "ljmp *%0\n\t" \ "cmpl %%ecx,last_task_used_math\n\t" \ "jne 1f\n\t" \ "clts\n" \ "1:" \ ::"m" (*&__tmp.a),"m" (*&__tmp.b), \ "d" (_TSS(n)),"c" ((long) task[n])); \ } 

I think that "ljmp %0\n\t" will work to change TSS and LDT. I know that the ljmp needs two parameters, for example ljmp $section, $offset . I think the ljmp instruction should use _TSS(n), xx . We do not need to provide a meaningful offset value because cpu changes the case of cpu, including eip for a new task.

  • I do not know how ljmp %0 works like ljmp $section, $offset and why this instruction uses %0 . Is %0 address __tmp.a ?

  • The CPU can save the EIP register in TSS for the old task when the ljmp instruction is ljmp . Is it correct that the EIP value for the old task is the address "cmpl %%ecx,_last_task_used_math\n\t" ?

+7
source share
1 answer

What does this syntax mean?

This unreadable mess is GCC Extended ASM , which has a common format

  asm [volatile] ( AssemblerTemplate : OutputOperands [ : InputOperands [ : Clobbers ] ] ) 

In this case, the __asm__ operator contains only AssemblerTemplate and InputOperands . Part of the input operands explains what %0 and %1 mean, and how ecx and edx get their meaning:

  • The first input operand is "m" (*&__tmp.a) , so %0 becomes m emory address __tmp.a (to be completely honest, I'm not sure why *& is needed here).
  • The second input operand is "m" (*&__tmp.b) , so %1 becomes the m emory address __tmp.b .
  • The third input operand is "d" (_TSS(n)) , so register D X will contain _TSS(n) when this code is run.
  • The fourth input operand "c" ((long) task[n]) , so the E C X register will contain task[n] when this code is run.

When cleaning, the code can be interpreted as follows

  cmpl %ecx, _current je 1f movw %dx, __tmp.b ;; the address of __tmp.b xchgl %ecx, _current ljmp __tmp.a ;; the address of __tmp.a cmpl %ecx, _last_task_used_math jne 1f clts 1: 

How does ljmp %0 ?

Note that there are two forms of the ljmp instruction (also called jmpf ). The one you know ( EA operation code) takes two immediate arguments: one for the segment, one for the offset. The one used here (operation code FF /5 ) is different: the segment and address arguments are not in the code stream, but somewhere in the memory, and the instruction points to the address.

In this case, the ljmp argument points to the __tmp structure at the __tmp . The first four bytes ( __tmp.a ) contain the offset, and the next two bytes (the lower half of __tmp.b ) contain the segment.

This indirect ljmp __tmp.a will be equivalent to ljmp [__tmp.b]:[__tmp.a] , except that ljmp segment:offset can only accept immediate arguments. If you want to switch to arbitrary TSS without self-modifying code (which would be a terrible idea), an indirect instruction will be used.

Also note that __tmp.a never initialized. We can assume that _TSS(n) refers to the shutter of the task (because this is how you switch the context from TSS), and the offset for the transitions through the task gate is ignored.

Where is the old instruction pointer located?

This piece of code does not store the old EIP in TSS.

(I think after this point, but I think this assumption is reasonable.)

The old EIP is stored in the kernel space stack, which corresponds to the old task.

Linux 0.11 allocates a stack of 0 (for the stack) for each task (see the copy_process function in fork.c , which initializes TSS). When an interrupt occurs during task A, the old EIP is stored in the kernel space stack, not in the user space stack. If the kernel decides to switch to task B, the kernel stack also switches. When the kernel eventually switches to task A, this stack switches back, and through iret we can return to where we were in task A.

+4
source

Source: https://habr.com/ru/post/1236243/


All Articles