2016-11-09 4 views
2

JMP instruction referece.Использование JMP инструкция к сегменту непостоянная TSS

Согласно документации, мы можем выполнить jmp до постоянного дальнего сегмента:

jmp 0x18:00 

Здесь 0x18 действительный селектор сегмента в GDT, глобальная таблица дескрипторов.

jmp может быть использован с сегментным регистром, который содержит действительную запись GDT, то есть дескриптор сегмента кода/данных:

mov es, 0x18 
jmp es:0x0 

Здесь 0x18 является TSS (Task государственного сегмента) Дескриптор, что при переходе to, CPU выполняет переключатель задачи, который автоматически сохраняет свое состояние в текущий TSS, а затем заполняется состоянием, сохраненным в новом TSS.

Однако TSS является дескриптором системного сегмента и, следовательно, не может быть загружен ни в один из регистров сегментов (как предложено в документе Intel). Тогда как, могу ли я перейти к задаче во время выполнения с динамически распределенным TSS?

Единственный способ, которым я могу думать, это использовать инструкцию iret, но я чувствую, что это как взломать, поскольку мне нужно изменить поле ссылки, а затем установить бит NT в EFLAGS для выполнения переключения задачи обратной ссылки.

+0

Но 'ltr' не вызывает переключатель задачи, то есть текущее состояние автоматически сохраняется. Но я думаю, что я попытаюсь переключиться на новую задачу (задача без прерывания, обработчики прерываний по мере того, как задачи уже работают), а затем обходите это ограничение позже. – Amumu

+0

Я неправильно понял вопрос. –

+1

Я не уверен, почему вы не можете использовать JMP для ссылки на память в качестве операнда. Операнд памяти будет указывать на адрес памяти, содержащий селектор/смещение. Этот адрес памяти может быть изменен во время выполнения –

ответ

4

Не только вы не можете загрузить ES с селектором TSS, инструкция jmp es:0x0 также инвалид. Нет инструкции, которая перемещает регистр сегмента в другой регистр сегмента (например, ES на CS). Также нет инструкции, которая будет загружать CS из общего регистра. Как показывает ответ Маргарет Блум, вам понадобится загрузить CS с инструкцией JMP, которая берет операнд памяти, в частности тот, который принимает дальний указатель в качестве операнда памяти, поэтому вы получаете инструкцию с большим прыжком, которая устанавливает CS.

Что касается реализации этого, было бы целесообразно включить этот далеко указатель в структуру вашей задачи, структуру, в которой вы размещаете TSS задачи и другую информацию, относящуюся к конкретной задаче. Например, для переключения задач, которые вы могли бы использовать такой код:

struct task { 
    struct { 
     unsigned offset; 
     unsigned short selector; 
    } far_jmp_ptr; 
    struct tss tss; 
    // ... 
}; 

void 
switch_tasks(struct task *new_task) { 
    asm("jmp FAR PTR %0" : : "m" (new_task->far_jmp_ptr)); 
} 

код предполагает «структуру задачи» с дальним указателем, который содержит выделенный селектор TSS для выполнения этой задачи (смещение части игнорируется).

Технически вы также можете перейти к задаче с помощью команды LTR, за которой следует инструкция JMP. Это изменяет задачу без выполнения переключения задачи, поэтому не регистрируются никакие регистры (кроме TR, CS: EIP и любых других регистров, которые вы явно меняете). Например:

mov esi, [new_task] 
ltr [esi + TASK_FAR_JMP_PTR + 4] 
jmp [esi + TASK_TSS + TSS_EIP] 

Это было бы только практичным, если новая задача выполняется в кольце 0 и либо только начинается, либо была остановлена ​​в известной точке, где это не нужно его регистров восстанавливается. В частности, так вы можете запустить начальную задачу ядра (или единственную задачу в одной операционной системе TSS.)

Обратите внимание, что в большинстве операционных систем используется только один TSS для всех задач, поэтому не используйте механизм переключения задач, предоставляемый процессором. Для 64-разрядных операционных систем это требуется, поскольку переключение задач не поддерживается в длинном режиме.

+0

Спасибо за всесторонний ответ. О форме 'jmp es: 0x0', GAS не отклоняет ее. Это сгенерированный код из этого sytnax: '26 ff 25 00 00 00 00 jmp *% es: 0x0' (выход objdump). – Amumu

+0

@Amumu Ваше сообщение не использовало синтаксис AT & T GAS, поэтому мой ответ и Маргарет Блумс использовали синтаксис NASM, который является синтаксисом, который наиболее точно напоминает код сборки в вашем сообщении. GAS генерировал почти косвенный jmp с нагрузками EIP с 32-битным значением, хранящимся в ES: 0, и вообще не изменяет CS (или задачу). –

+0

Ну, я использовал GAS, но настроил его на синтаксис Intel. – Amumu

5
push WORD <TSS_selector> 
push DWORD 0 
jmp FAR [esp] 

Предполагая 32-разрядный код и используемый стек.
Это оставит стек несбалансированное и выровненные в вызывающем потоке, вы можете использовать специальную ячейку памяти:

mov WORD [tss_pointer + 4], <TSS_selector> 
jmp FAR [tss_pointer] 

tss_pointer dd 0, dw 0 
+1

В зависимости от вашего контекста вы можете уменьшить этот код, используя красный стек: cli; mov word [esp + 2], TSS_selector; mov dword [esp + 6], 0 sti; jmp far [esp + 6]. В пользовательском режиме опускают инструкции cli и sti (но ваш код будет сбой при подключении отладчика). Этот код зависит от неясного факта, что прерывание не может быть разрешено сразу после инструкции sti, но только одна инструкция после. – Joshua

+0

Спасибо за ваше предложение. Это быстро дало мне первоначальную идею обнаружить, где я был неправ. – Amumu

-1

Предлагаемые ответы здесь верны, но есть недостающая часть: предложенный синтаксис не создает длинный прыжок. Я сделал так, как Margaret Bloom предложил, но это не сработало. В моем коде должно быть что-то не так, так как я знаю, что она дала мне правильный ответ, так как другие также предлагают одно и то же. Глядя на GDB, когда я применил выше синтаксис:

asm("pushw 0xa0"); 
asm("pushd 0x0"); 
asm("jmp far [esp]"); 

(приведенный выше синтаксис встроенный ассемблер, GCC, стиль)

Глядя на GDB, jmp far генерируется как:

0x30a9 <task1_start+1> mov ebp,esp 
0x30ab <task1_start+3> pushw 0xa0 
0x30af <task1_start+7> push 0x0 
0x30b1 <task1_start+9> jmp DWORD PTR [esp+0xff06] 

Очевидно, , [esp + 0xff06] не выглядит очень далеко от меня. Это близкий прыжок со смещением от esp. Более очевидно, с выходом objdump:

000030a8 <task1_start>: 
    30a8:  55      push %ebp 
    30a9:  89 e5     mov %esp,%ebp 
    30ab:  66 68 a0 00    pushw $0xa0 
    30af:  6a 00     push $0x0 
    30b1:  ff a4 24 06 ff 00 00 jmp *0xff06(%esp) 
    30b8:  90      nop 
    30b9:  5d      pop %ebp 
    30ba:  c3      ret 

Обратите внимание на опкод в 0x30ab, что соответствует jmp инструкции. Глядя на руководство Intel, этот опкод для близкого перехода:

  • 0xff означает jmp инструкции.
  • 0xa4 - это ModR/M байт для [--][--] + disp32 эффективный адрес для esp. Это означает, что требуется байт SiB, который является смещением. (ссылка: Таблица 2-2. 32-битные формы адресации с байтом ModR/M)
  • 0x24 - это байтовые байты SiB для ESP, но без какого-либо масштабирования (значение none), эффективно, сохраняйте его. (ссылка: Таблица 2-3. 32-битные формы адресации с байтом SIB).

выше генерируется jmp соответствует FF /4 опкода (ссылка: jmp instruction), что означает почти скачок, так как генерируется ModR/М байт 0xa4. Правильный код операции для дальнего прыжка - FF /5.

Очевидно, что я должен сделать что-то для ассемблера, чтобы генерировать длинный прыжок. Таким образом, оказалось, что это легко исправить с помощью ljmp инструкции вместо jmp far синтаксис как так:

ljmp [esp] 

После этого мы получили правильно сгенерированный код:

00003088 <task1_start>: 
    3088:  55      push %ebp 
    3089:  89 e5     mov %esp,%ebp 
    308b:  66 68 a0 00    pushw $0xa0 
    308f:  6a 00     push $0x0 
    3091:  ff 2c 24    ljmp *(%esp) 
    3094:  90      nop 
    3095:  5d      pop %ebp 
    3096:  c3      ret 

В вышеприведенном ljmp является сгенерировано:

  • 0xff - код операции для jmp, то же самое. ljmp - это просто особый синтаксис, используемый GAS (GNU Assembler) для генерации кода операции FF /5.
  • 0x2c - это ModR/M байт для [--][--] (без смещения), но в столбце 5 в таблице 2-2. Это означает, что этот код операции действительно FF /5.
  • 0x24 то же самое для прыжка в ближайшее время, что означает отсутствие масштабирования.

И это фактический код рассматривается GDB:

0x308b <task1_start+3> pushw 0xa0   
0x308f <task1_start+7> push 0x0    
0x3091 <task1_start+9> jmp FWORD PTR [esp] 

Теперь FWORD что-то новое, но по крайней мере это не добавляет случайное смещение больше. И действительно, задача правильно переключена на 0xa0.

Спасибо за ваши предложения, всем. Без этого я бы никогда не исследовал это.

+0

С вашим ответом возникает ряд проблем. Вы не можете использовать встроенную сборку стиля GCC. Вам нужно сделать все в одном из утверждений, поскольку компилятор может вставлять другие инструкции между ними. Вы использовали 16-битный push, который смещал стек. Вы использовали синтаксис NASM вместо синтаксиса MASM версии GAS, поэтому 'jmp far [esp]' не работал. –

+0

Я установил эту опцию: '-masm = intel', поэтому GCC использует intel. Да, компилятор может вставлять инструкции между ними. Тем не менее, это просто возможность, и в том, что касается собственно сгенерированного кода, дополнительный код не был вставлен между этими инструкциями push и jump.Что касается 16-битного нажатия, это всего лишь демонстрация, за которой следует ответ от Маргарет. Вместо этого я исправлю использование памяти. Я использовал стек, чтобы проверить, работает ли он. – Amumu

+0

Другая проблема с вашей встроенной операцией сборки заключается в том, что старая задача возобновится в инструкции после даун-прыжка с восстановлением регистров до того, что было до прыжка. Это означает, что он сработает, поскольку дальнейший адрес, нажатый на стек, все равно будет там. –