3

I am trying to use Intel Pintool to monitor CALL and RET instructions on an x86-64 machine (Mac Pro). I'm passing IARG_INST_PTR (mentioned below) to the docount function and using the instruction-pointer I deduce the instruction by checking the opcode (CALL is 0xe8 and RET is 0xc3 from Intel x86-64 manual. However, it seems like this check is not completely accurate as I am noticing more number of RET than CALL for any given binary instrumented with this logic.

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

Could anyone please give me some pointers as to what I am doing wrong?

I have borrowed the template from /tools/ManualExamples/inscount0.cpp. To find it, search for the filename here.

phuclv
  • 37,963
  • 15
  • 156
  • 475
kaushik
  • 31
  • 3
  • 1
    Is it possible for compiled code to really have more `ret` than `call`? if (a) return 1; else if (b) return 2; else return 3; inside function might generate multiple `ret` – Severin Pappadeux Apr 18 '15 at 03:41
  • Unless the code is buggy there shouldn't be any mismatches. This means that my logic of CALL/RET detection must be flawed. Im hoping to get some pointers to correct the same. – kaushik Apr 18 '15 at 03:50
  • @SeverinPappadeux - multiple return statements will not result in separate RET instructions, they rather lead to JMP instructions to RET, with appropriate return value in RAX register. – kaushik Apr 18 '15 at 04:10
  • Well, execution time will be larger because it is JMP+RET vs just RET. But code size would be bigger. I would guess that under some optimization settings compiler might prefer to issue RETs everywhere instead of JMP+RET – Severin Pappadeux Apr 18 '15 at 15:45
  • @user1983710 My previous example was damn wrong. I reworked it, and I have a quite big difference between CALLs and RETs with more CALLs than RETs (tested on a simple console program on windows, namely `ipconfig.exe`): `CALL: 176298` , `RET: 170374`. I'm still trying to figure why (although it might be related to system internals). I'll try do build a pintool to log possibly unmatched pairs of CALL/RET. You have my +1! – Neitsa Apr 24 '15 at 23:09
  • @Neitsa: Thank you. I ran a pintool without limiting instrumentation of any system libraries, with the instrument and analysis function same as your previous answer. I found out that the mismatch is very less. For the binary /bin/ls, the count is `[*] Call count: 66282 [*] Ret count: 66280` For a hello_world program, the count is `[*] Call count: 44846 [*] Ret count: 44845` – kaushik Apr 24 '15 at 23:45
  • @Neitsa: Another observation - with the method above, the count difference is constant, although the actual call/ret counts change on each run. – kaushik Apr 24 '15 at 23:55

2 Answers2

0

There are various versions of CALLs with different opcodes so you can't just check for 0xE8. The full list can be found in Intel manual, call procedure section:

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.

The same to 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.

Note that the lines containing the same opcodes above are just for different modes (16/32/64-bit)

phuclv
  • 37,963
  • 15
  • 156
  • 475
  • Hi, I tried these (I am not sure how to match the REX.W + FF instruction), but now - Call count is 68183 and RET count is 44971 bool isRet(unsigned int valAtInstrPtr){ unsigned int foo = (valAtInstrPtr & 0xFF); return foo == 0xc3 || foo == 0xcb || foo == 0xc2 || foo == 0xca; } bool isCall(unsigned int valAtInstrPtr){ unsigned int foo = (valAtInstrPtr & 0xFF); return foo == 0xe8 || foo == 0xff || foo == 0x9a; } – kaushik Apr 18 '15 at 03:59
  • put codes into `backticks` or almost no one can read what you wrote. [REX prefix](http://wiki.osdev.org/X86-64_Instruction_Encoding#REX_prefix) is 0x4X. And 0xFF is not a prefix but indicates a 2-byte opcode so you'll need to check the next byte. But why don't feed the binary/hex output into a dissassembler and count? If you want to parse it yourself you must understant the x86_64 instruction format. They're not simply with a single opcode byte first followed by many parameters – phuclv Apr 18 '15 at 05:25
  • You should probably differentiate far calls from near calls; normal user-space code for mainstream OSes won't have any far calls. – Peter Cordes Jun 05 '19 at 03:17
0

There is not always a match between call and ret instructions simply because functions can be interrupted by exceptions, goto-like statement, longjumps, signals, etc...So if you want to re-conciliate calls and ret, you might want to take into considerations all of this.

This has been discussed already several times, especially here

Heyji
  • 1,113
  • 8
  • 26