Что вообще означает этот синтаксис?
Этот нечитаемый беспорядок представляет собой Extended ASM GCC, который имеет общий формат
asm [volatile] ( AssemblerTemplate
: OutputOperands
[ : InputOperands
[ : Clobbers ] ] )
В этом случае оператор __asm__
содержит только AssemblerTemplate
и InputOperands
. Часть входных операндов объясняет, что означают %0
и %1
и как ecx
и edx
получают свое значение:
- Первый входной операнд —
"m" (*&__tmp.a)
, поэтому %0
становится mадресом памяти __tmp.a
(честно говоря, я не уверен, зачем здесь нужен *&
).
- Второй входной операнд —
"m" (*&__tmp.b)
, поэтому %1
становится mадресом памяти __tmp.b
.
- Третий входной операнд —
"d" (_TSS(n))
, поэтому регистр DX будет содержать _TSS(n)
при запуске этого кода.
- Четвертый входной операнд —
"c" ((long) task[n])
, поэтому регистр ECX будет содержать task[n]
при запуске этого кода.
После очистки код можно интерпретировать следующим образом
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:
Как ljmp %0
вообще может работать?
Обратите внимание, что существует две формы инструкции ljmp
(также известной как jmpf
). Тот, который вы знаете (код операции EA
), принимает два непосредственных аргумента: один для сегмента, один для смещения. Используемый здесь (опкод FF /5
) отличается: аргументы сегмента и адреса находятся не в потоке кода, а где-то в памяти, а инструкция указывает на адрес.
В этом случае аргумент ljmp
указывает в начале на структуру __tmp
. Первые четыре байта (__tmp.a
) содержат смещение, а следующие два байта (младшая половина __tmp.b
) содержат сегмент.
Этот косвенный ljmp __tmp.a
будет эквивалентен ljmp [__tmp.b]:[__tmp.a]
, за исключением того, что ljmp segment:offset
может принимать только непосредственные аргументы. Если вы хотите переключиться на произвольный TSS без самомодифицирующегося кода (что было бы ужасной идеей), используйте косвенную инструкцию.
Также обратите внимание, что __tmp.a
никогда не инициализируется. Мы можем предположить, что _TSS(n)
относится к шлюзу задачи (потому что именно так вы выполняете переключение контекста с помощью TSS), а смещение для переходов «сквозь» шлюз задачи игнорируется.
Куда делся старый указатель инструкций?
Этот фрагмент кода не сохраняет старый EIP в TSS.
(Я предполагаю, что после этого момента, но я думаю, что это предположение разумно.)
Старый EIP хранится в стеке пространства ядра, соответствующем старой задаче.
Linux 0.11 выделяет кольцевой стек 0 (т. е. стек для ядра) для каждой задачи (см. функцию copy_process
в fork.c
, которая инициализирует TSS). Когда во время выполнения задачи A происходит прерывание, старый EIP сохраняется в стеке пространства ядра, а не в стеке пользовательского пространства. Если ядро решает переключиться на задачу B, стек пространства ядра также переключается. Когда ядро в конце концов переключается обратно на задачу А, этот стек переключается обратно, и через iret
мы можем вернуться туда, где мы были в задаче А.
person
Community
schedule
19.11.2015
ljmp %0
перейдет к 48-битному адресу, содержащемуся в памяти по адресу %0. Так что эффективно он будетljmp
по адресу, содержащемуся в памяти по адресу__tmp
. Вы заметите, чтоmovw %%dx,%1
фактически инициализирует__tmp.b
значением_TSS(n)
. _TSS(n) будет дескриптором сегмента для шлюза задачи. Вы заметите, что%0
(__tmp.a) не инициализирован. В этом нет необходимости, поскольку смещение (которое представляет __tmp.a) игнорируется, когда вы ljmp проходите через шлюз задачи. Фактически вы делаете ljmp для сегмента: смещение _TSS (n): мусор. - person Michael Petch   schedule 18.11.2015cmpl %%ecx,_last_task_used_math
будет выполняться в контексте новой задачи после ljmp. Я не смотрел исходный код старого ядра, но кажется, что _last_task_used_math — это идентификатор последней задачи, в которой использовалась математическая инструкция. Если он отличается от текущего идентификатора задачи, инструкцияclts
не используется. - person Michael Petch   schedule 18.11.2015ljmp TSS(n):garbage
было упрощением, и я должен извиниться. Сам указатель на самом деле находится в tmp . Используемая формаljmp
представляет собой косвенный длинный переход, где один параметр является указателем на ячейку памяти, которая содержит указатель для перехода. Интересно то, что использовавшийся ассемблер (2 десятилетия назад) был более слабым в отношении синтаксиса дляljmp
. Чтобы избежать путаницы, если вы хотите обозначить косвенный дальний jmp через указатель, он будет выглядеть какljmp *%0
. Звездочка говорит, что мы переходим к адресу, содержащемуся в ячейке памяти (%0 — это адрес tmp) - person Michael Petch   schedule 19.11.2015ljmp
(при использовании целевого адреса, который использует дескриптор задачи) - person Michael Petch   schedule 19.11.2015