Прыжки для JIT (x86 64)
Я пишу JIT-компилятор на C для x86_64 linux.
В настоящее время идея заключается в том, чтобы сгенерировать некоторый байт-код в буфере исполняемой памяти (например, полученный с помощью вызова mmap) и перейти к нему с помощью указателя функции.Я хотел бы иметь возможность связать несколько блоков исполняемой памяти вместе так, чтобы они могли прыгать между собой, используя только собственные инструкции.
В идеале указатель уровня C на исполняемый блок может быть записан в другой блок как абсолютный перейти адрес примерно так:
unsigned char *code_1 = { 0xAB, 0xCD, ... };
void *exec_block_1 = mmap(code1, ... );
write_bytecode(code_1, code_block_1);
...
unsigned char *code_2 = { 0xAB, 0xCD, ... , exec_block_1, ... };
void *exec_block_2 = mmap(code2, ... );
write_bytecode(code_2, exec_block_2); // bytecode contains code_block_1 as a jump
// address so that the code in the second block
// can jump to the code in the first block
Однако я нахожу ограничения x86_64 довольно серьезным препятствием здесь. В x86_64 невозможно перейти к абсолютному 64-разрядному адресу, так как все доступные 64-разрядные операции перехода относятся к указателю инструкции. Это означает, что я не могу использовать C-указатель в качестве цели перехода для сгенерированного кода.
Существует ли решение этой проблемы, которое позволит мне связать блоки вместе описанным выше способом? Возможно, инструкция x86_64 о чем я не знаю?
2 ответа:
Хм, Я не уверен, что ясно понял ваш вопрос, и если это правильный ответ. это довольно запутанный способ достичь этого:
;instr ; opcodes [op size] (comment) call next ; e8 00 00 00 00 [4] (call to get current location) next: pop rax ; 58 [1] (next label address in rax) add rax, 12h ; 48 83 c0 12 [4] (adjust rax to fall on landing label) push rax ; 50 [1] (push adjusted value) mov rax, code_block ; 48 b8 XX XX XX XX XX XX XX XX [10] (load target address) push rax ; 50 [1] (push to ret to code_block) ret ; c3 [1] (go to code_block) landing: nop nop
e8 00 00 00 00
просто там, чтобы получить текущий указатель на вершине стека. Затем код настраиваетrax
, чтобы позже упасть на посадочную метку. Вам нужно будет заменитьXX
(вmov rax, code_block
) виртуальным адресомcode block
. В качестве вызова используется инструкцияret
. Когда вызывающий объект возвращается, код должен упасть наlanding
.Это что-то вроде того, что ты делаешь? пытаешься чего-то добиться?
Если вы знаете адреса блоков в момент, когда вы посылаете команды перехода, вы можете просто проверить, соответствует ли расстояние в байтах от адреса команды перехода до адреса целевого блока 32-битному знаковому смещению семейства инструкций
jXX
.Даже если вы
mmap
каждый блок отдельно, шансы довольно велики, что вы не получите два соседних (В смысле потока управления) блока, которые находятся более чем на ±2GiB друг от друга. То существо есть несколько веских причин не сопоставлять каждый блок отдельно таким образом. Во-первых, минимальная единица выделенияmmap
- это (почти по определению) страница, которая, вероятно, составляет не менее 4 КБ. Это означает, что неиспользуемое пространство после кода для каждого блока тратится впустую. Во-вторых, более плотная упаковка базовых блоков увеличивает использование кэша команд и вероятность того, что более короткое кодирование перехода будет допустимым.Возможно, инструкция x86_64, которую я не в курсе?
Кстати, есть инструкция для загрузки 64-битного непосредственного в
rax
. Набор инструментов GNU ссылается на него какmovabs
:Поэтому, если вы действительно хотите, вы можете просто загрузить указатель в0000000000000000 <.text>: 0: 49 b8 ff ff ff ff ff movabs rax,0x7fffffffffffffff 7: ff ff 7f
rax
и использовать прыжок для регистрации.