https://github.com/akkartik/mu/blob/main/linux/bootstrap/019functions.cc
  1 //:: call
  2 
  3 :(before "End Initialize Op Names")
  4 put_new(Name, "e8", "call disp32 (call)");
  5 
  6 :(code)
  7 void test_call_disp32() {
  8   Mem.push_back(vma(0xbd000000));  // manually allocate memory
  9   Reg[ESP].u = 0xbd000064;
 10   run(
 11       "== code 0x1\n"
 12       // op     ModR/M  SIB   displacement  immediate
 13       "  e8                                 a0 00 00 00 \n"  // call function offset at 0xa0
 14       // next EIP is 6
 15   );
 16   CHECK_TRACE_CONTENTS(
 17       "run: call imm32 0x000000a0\n"
 18       "run: decrementing ESP to 0xbd000060\n"
 19       "run: pushing value 0x00000006\n"
 20       "run: jumping to 0x000000a6\n"
 21   );
 22 }
 23 
 24 :(before "End Single-Byte Opcodes")
 25 case 0xe8: {  // call disp32 relative to next EIP
 26   const int32_t offset = next32();
 27   ++Callstack_depth;
 28   trace(Callstack_depth+1, "run") << "call imm32 0x" << HEXWORD << offset << end();
 29 //?   cerr << "push: EIP: " << EIP << " => " << Reg[ESP].u << '\n';
 30   push(EIP);
 31   EIP += offset;
 32   trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
 33   break;
 34 }
 35 
 36 //:
 37 
 38 :(code)
 39 void test_call_r32() {
 40   Mem.push_back(vma(0xbd000000));  // manually allocate memory
 41   Reg[ESP].u = 0xbd000064;
 42   Reg[EBX].u = 0x000000a0;
 43   run(
 44       "== code 0x1\n"
 45       // op     ModR/M  SIB   displacement  immediate
 46       "  ff     d3                                      \n"  // call function offset at EBX
 47       // next EIP is 3
 48   );
 49   CHECK_TRACE_CONTENTS(
 50       "run: call to r/m32\n"
 51       "run: r/m32 is EBX\n"
 52       "run: decrementing ESP to 0xbd000060\n"
 53       "run: pushing value 0x00000003\n"
 54       "run: jumping to 0x000000a0\n"
 55   );
 56 }
 57 
 58 :(before "End Op ff Subops")
 59 case 2: {  // call function pointer at r/m32
 60   trace(Callstack_depth+1, "run") << "call to r/m32" << end();
 61   const int32_t* offset = effective_address(modrm);
 62   push(EIP);
 63   EIP = *offset;
 64   trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
 65   ++Callstack_depth;
 66   break;
 67 }
 68 
 69 :(code)
 70 void test_call_mem_at_rm32() {
 71   Mem.push_back(vma(0xbd000000));  // manually allocate memory
 72   Reg[ESP].u = 0xbd000064;
 73   Reg[EBX].u = 0x2000;
 74   run(
 75       "== code 0x1\n"
 76       // op     ModR/M  SIB   displacement  immediate
 77       "  ff     13                                      \n"  // call function offset at *EBX
 78       // next EIP is 3
 79       "== data 0x2000\n"
 80       "a0 00 00 00\n"  // 0xa0
 81   );
 82   CHECK_TRACE_CONTENTS(
 83       "run: call to r/m32\n"
 84       "run: effective address is 0x00002000 (EBX)\n"
 85       "run: decrementing ESP to 0xbd000060\n"
 86       "run: pushing value 0x00000003\n"
 87       "run: jumping to 0x000000a0\n"
 88   );
 89 }
 90 
 91 //:: ret
 92 
 93 :(before "End Initialize Op Names")
 94 put_new(Name, "c3", "return from most recent unfinished call (ret)");
 95 
 96 :(code)
 97 void test_ret() {
 98   Mem.push_back(vma(0xbd000000));  // manually allocate memory
 99   Reg[ESP].u = 0xbd000064;
100   write_mem_u32(Reg[ESP].u, 0x10);
101   run(
102       "== code 0x1\n"
103       // op     ModR/M  SIB   displacement  immediate
104       "  c3                                           \n"  // return
105       "== data 0x2000\n"
106       "10 00 00 00\n"  // 0x10
107   );
108   CHECK_TRACE_CONTENTS(
109       "run: return\n"
110       "run: popping value 0x00000010\n"
111       "run: jumping to 0x00000010\n"
112   );
113 }
114 
115 :(before "End Single-Byte Opcodes")
116 case 0xc3: {  // return from a call
117   trace(Callstack_depth+1, "run") << "return" << end();
118   --Callstack_depth;
119   EIP = pop();
120   trace(Callstack_depth+1, "run") << "jumping to 0x" << HEXWORD << EIP << end();
121   break;
122 }