Количество инструкций CALL и RET, отслеживающих несоответствие при использовании Intel Pin

Я пытаюсь использовать Intel Pintool для мониторинга инструкций CALL и RET на машине x86-64 (Mac Pro). Я передаю IARG_INST_PTR (упомянутый ниже) функции docount и с помощью указателя инструкций вывожу инструкцию, проверяя код операции (CALL — это 0xe8, а RET — это 0xc3 из Intel x86- Руководство 64. Однако кажется, что эта проверка не совсем точна, поскольку я замечаю большее количество RET, чем CALL для любого данного двоичного файла, оснащенного этой логикой.

INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_CONTEXT,
    IARG_INST_PTR, IARG_END);

Может ли кто-нибудь дать мне несколько советов относительно того, что я делаю неправильно?

Я позаимствовал шаблон у /tools/ManualExamples/inscount0.cpp. Чтобы найти его, найдите имя файла здесь.


person kaushik    schedule 18.04.2015    source источник
comment
Возможно ли, чтобы скомпилированный код действительно содержал больше ret, чем call? если (а) вернуть 1; иначе, если (б) вернуть 2; иначе вернуть 3; внутренняя функция может генерировать несколько ret   -  person Severin Pappadeux    schedule 18.04.2015
comment
Если код не содержит ошибок, несоответствий быть не должно. Это означает, что моя логика обнаружения CALL/RET должна быть ошибочной. Я надеюсь получить некоторые указатели, чтобы исправить то же самое.   -  person kaushik    schedule 18.04.2015
comment
@SeverinPappadeux - несколько операторов возврата не приведут к отдельным инструкциям RET, они скорее приведут к инструкциям JMP для RET с соответствующим возвращаемым значением в регистре RAX.   -  person kaushik    schedule 18.04.2015
comment
Что ж, время выполнения будет больше, потому что это JMP+RET, а не просто RET. Но размер кода будет больше. Я предполагаю, что при некоторых настройках оптимизации компилятор может предпочесть везде выдавать RET вместо JMP+RET   -  person Severin Pappadeux    schedule 18.04.2015
comment
@user1983710 user1983710 Мой предыдущий пример был чертовски неправильным. Я переработал его, и у меня есть довольно большая разница между CALL и RET с большим количеством CALL, чем RET (проверено на простой консольной программе в Windows, а именно ipconfig.exe): CALL: 176298 , RET: 170374. Я все еще пытаюсь понять, почему (хотя это может быть связано с внутренними компонентами системы). Я попытаюсь создать пин-инструмент для регистрации, возможно, непарных пар CALL/RET. У тебя есть мой +1!   -  person Neitsa    schedule 25.04.2015
comment
@Neitsa: Спасибо. Я запустил pintool, не ограничивая инструментарий каких-либо системных библиотек, с инструментом и функцией анализа, такими же, как в вашем предыдущем ответе. Я обнаружил, что несоответствие очень меньше. Для двоичного файла /bin/ls количество равно [*] Call count: 66282 [*] Ret count: 66280 Для программы hello_world количество равно [*] Call count: 44846 [*] Ret count: 44845.   -  person kaushik    schedule 25.04.2015
comment
@Neitsa: Еще одно наблюдение - при использовании описанного выше метода разница в счете постоянна, хотя фактическое количество вызовов/возвратов меняется при каждом запуске.   -  person kaushik    schedule 25.04.2015


Ответы (2)


Не всегда есть совпадение между инструкциями call и ret просто потому, что функции могут быть прерваны исключениями, оператором goto, длинными переходами, сигналами и т. д. Поэтому, если вы хотите повторно согласовать вызовы и ret, вы можете взять во внимание все это.

Это обсуждалось уже несколько раз, особенно здесь

person Heyji    schedule 14.09.2015

Существуют различные версии CALL с разными кодами операций, поэтому вы не можете просто проверить наличие 0xE8. Полный список можно найти в руководстве Intel, раздел процедура вызова:

Opcode       Instruction    Description
E8 cw        CALL rel16     Call near, relative, displacement relative to next instruction
E8 cd        CALL rel32     Call near, relative, displacement relative to next instruction
                               32-bit displacement sign extended to 64-bits in 64-bit mode.
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.
FF /2        CALL r/m64     Call near, absolute indirect, address given in r/m64.
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.
                               In 32-bit mode: if selector points to a gate, then
                               RIP = 32-bit zero extended displacement taken from gate; else
                               RIP = zero extended 16-bit offset from far pointer
                               referenced in the instruction.
FF /3        CALL m16:32    In 64-bit mode: If selector points to a gate, then
                               RIP = 64-bit displacement taken from gate; else
                               RIP = zero extended 32-bit offset from far pointer
                               referenced in the instruction.
REX.W FF /3  CALL m16:64    In 64-bit mode: If selector points to a gate, then
                               RIP = 64-bit displacement taken from gate; else
                               RIP = 64-bit offset from far pointer
                               referenced in the instruction.

То же самое для RET

Opcode* Instruction     Description
C3      RET             Near return to calling procedure.
CB      RET             Far return to calling procedure.
C2 iw   RET imm16       Near return to calling procedure and pop imm16 bytes from stack.
CA iw   RET imm16       Far return to calling procedure and pop imm16 bytes from stack.

Обратите внимание, что строки, содержащие одинаковые коды операций выше, предназначены только для разных режимов (16/32/64-бит).

person phuclv    schedule 18.04.2015
comment
Привет, я попробовал это (я не уверен, как сопоставить инструкцию REX.W + FF), но теперь - счетчик вызовов равен 68183, а счетчик RET равен 44971 bool isRet(unsigned int valAtInstrPtr){ unsigned int foo = (valAtInstrPtr & 0xFF ); вернуть foo == 0xc3 || фу == 0xcb || фу == 0xc2 || фу == 0xca; } bool isCall(unsigned int valAtInstrPtr){ unsigned int foo = (valAtInstrPtr & 0xFF); вернуть foo == 0xe8 || фу == 0xff || фу == 0x9a; } - person kaushik; 18.04.2015
comment
вводите коды в backticks или почти никто не сможет прочитать то, что вы написали. префикс REX равен 0x4X. И 0xFF не является префиксом, а указывает на 2-байтовый код операции, поэтому вам нужно будет проверить следующий байт. Но почему бы не передать двоичный/шестнадцатеричный вывод в дизассемблер и не посчитать? Если вы хотите разобрать его самостоятельно, вы должны понимать формат инструкций x86_64. Они не просто с одним байтом кода операции, за которым следует множество параметров. - person phuclv; 18.04.2015
comment
Вероятно, вам следует отличать дальние вызовы от ближних; обычный код пользовательского пространства для основных ОС не будет иметь дальних вызовов. - person Peter Cordes; 05.06.2019