Прыжки для 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 7fraxи использовать прыжок для регистрации.