В разрабатываемой программке есть компилятор из строки - математического выражения в непосредственно исполняемый код. функция получается такая, что не изменяет EBX. сразу за кодом возврата пушатся временно параметры, которые переполняли бы стек FPU (ну, если выражение сложное), потом, конечно, забираются из стека так, что к ret уже ESP указывает на то же место, что и после call. воспринимает два параметра в EAX и EDX (_fastcall Borland). эти регистры не изменяются кодом функции тоже. Так вот: мне нужно иметь возможность из этой функции вызывать любую функцию типа double _stdcall f(void), которая статически присутствует уже. Для этого нужно добавить call. Есть много разных call-ов: E8 cw CALL rel16 Call near, relative, displacement relative to next instruction E8 cd CALL rel32 Call near, relative, displacement relative to next instruction FF /2 CALL r/m16 Call near, absolute indirect, address given in r/m16 FF /2 CALL r/m32 Call near, absolute indirect, address given in r/m32 9A cd CALL ptr16:16 Call far, absolute, address given in operand 9A cp CALL ptr16:32 Call far, absolute, address given in operand FF /3 CALL m16:16 Call far, absolute indirect, address given in m16:16 FF /3 CALL m16:32 Call far, absolute indirect, address given in m16:32 Мне нужен absolute, address given in operand. Операнд у меня есть - это &f - адрес точки входа. Никакие регистры явно я не хочу портить вообще, так что asm {mov EAX, offset f; call EAX;} меня не интересуют; как и вариант asm {push EAX, mov EAX, offset f; call EAX; pop EAX;} - этот ввиду своей неоптимальности (функция - целевая в задаче оптимизации). Я попробовал вариант с опкодом 9A CALL ptr16:32: __emit__(0x9A, &f, 0x00, 0x00); плохо понимая, что есть "16:" и заполняя его нулями получаю ошибку. Можно использовать конечно лишнюю переменную: void * pf = &f; и делать asm {call dword ptr [pf];}. Но из-за приступа перфекционизма и пытливости (надо же разобраться до конца!) не хочу создавать эту переменную. Да и call с address given in operand наверное быстрее выполняется, чем, если читать адрес из памяти (чуть-чуть ). Подскажите как сформировать опкод? Идеален был бы вариант CALL ptr32.
Может быть, таки нужен "call near, relative, displacement relative to next instruction"? Пример: Код (Text): 1000: db E8 34 12 00 00 ; call Destination. Destination == 1000 + 5 + 1234. ... 2239: ; Destination "16:" определяет селектор. В твоём случае этот формат не нужен.
нет. не этот. код функции может быть перемещён или копирован (это я говорю из общности - я не могу предположить как я буду модернезировать эту программку в дальнейшем) и relative изменится. Селектор как я понял означает, что адрес считается из комбинации EIP и этого селектора. У меня возникает ассоциация по этому поводу только такая: CS:EIP. При отладке я подсмотрел, что перед вызовом {__emit__(0x9A, &f, 0x00, 0x00);} CS = 0x001B. Делаю пробник так: __emit__(0x9A, &f, 0x1B, 0x00); - всё получается. Но вот вопрос тогда вот в чём: селектор получается что-то "выбирает", - судя по названию. То есть мы можем CS изменить и будет AV (ну или не мы - какая-нибудь функция, неизвестно как устроенная). Вот мне интересно, могу ли я рассчитывать на то, что CS в ходе исполнения программы не изменится? Позапускал программку несколько раз - одно и то же значение вроде-бы...
всё. вопрос исчерпан. по тикам с этими селекторами значительно проигрывает вариант варианту с переменной FF /2 CALL r/m32 Call near, absolute indirect, address given in r/m32
А, тогда да. Разве что хранить вместе с функцией смещения, по которым при перемещении фиксить адреса. Формально – нет. Фактически же вся 32-битная линейка NT использует селектор 1B для определения кодового сегмента, AFAIK. Но, опять-таки, если жёстко зашить селектор 1B, то будет крах при выполнении на x64 – тот сегмент там доступен только для нулевого кольца, да и не исполняемый он к тому же.
Код (Text): call Delta Delta: add dword ptr [esp],(offset Return - offset Delta) push BranchAddress ret Return:
Clerk Не пойдёт. Один хочет создавать сверхпроизводительную функцию, а push'n'ret полностью пригнёт вверх предсказания возвратов. Теоретически; замеров мы не делали.
Sol_Ksacap Пусть разбавят другими инструкциями, тогда простоев не будет. Инструкции которые могут измянь кодовый селектор инициируют множество проверок, что сказывается на времени, поэтому наверно межсегментное ветвление не подходит. Тогда самый оптимальный вариант - модификация кода на лету, вычисляйте смещение динамически и записывайте его при формировании кода. В таком случае код будет не перемещаемым. Как показывает практика, в юзермоде при включённом планировании подобная оптимизация не имеет смысла.
Статью нашёл http://www.insidepro.com/kk/116r.shtml. Знаток пишет, что есть некий опкод "EB YYh YYh YYh YYh" и это "непосредственный CALL". У меня этот опкод расшифровывается как jmp (signed char)YYh (первый) - всего 2 байта опкод - и дальше всякая белебирда. В чём Крис ошибся? или я ошибся?
Это опечатка, имеется ввиду относительный вызов E8 rel32 Вот эта фраза тоже сомнительна: т.к. в современных процах рулит предсказание переходов и начиная со второго вызова функции проц будет переходить по ранее запомненному в BTB адресу не дожидаясь начала\завершения чтения [addr], а потеря 0.3-1 такта на само чтение [addr] это мелочь по сравнению даже с call+ret, не говоря уже о теле функции