函數指針與指針函數,直接向內存區域寫入操做碼並轉成函數指針並調用

函數指針與指針函數

閱讀hotspot的代碼,須要先弄清楚這兩個很容易混淆的概念。
最近我想到一個辦法去區分折扣兩個,就是在中間加個「的」:java

  • 函數的指針
  • 指針的函數

這樣就很容易區分了。數組

  • 函數指針,其實是指針,是指向函數的指針
  • 指針函數,實際上函數,是指返回指針的函數

其實在C語言中,指針數組和數組指針也是同樣的區分辦法。 加個「的」即可以解決。
爲何會有這樣的問題?老外怎麼理解這個事情?我得回去翻翻K&R的那本C的英文教材。TODO。函數

指針函數

是指返回指針的函數,好比:指針

int *fun (int a, int b) {
    int c = a + b;
    return &c; // 返回一個指針
}

函數指針

是指指向函數的指針code

#include <stdio.h>

int (* fun)(int a, int b); // fun 就是指向函數的指針變量 

int add (int a, int b) {
    int c = a + b;
    return c;
}

int main(){
    fun = add;
    int result = fun(100, 200);
    printf("result=%d\n", result);
}

函數指針定義成類型

還能夠結合類型聲明將函數指針定義成類型blog

#include <stdio.h>

// 定義一個 返回值是int 參數是兩個int的函數指針
typedef int (* FunRefTypeTwoIntArgs)(int a, int b); // 將上面的函數指針變量 演化爲類型 FunRefTypeTwoIntArgs 就是個類型 這樣就好用多了

FunRefTypeTwoIntArgs addFun;
FunRefTypeTwoIntArgs subFun;

int add (int a, int b) {
    int c = a + b;
    return c;
}

int sub (int a, int b) {
    int c = a - b;
    return c;
}

int main(){
    addFun = add;
    int result = addFun(100, 200);
    printf("result=%d\n", result);
    
    subFun = add;
    int result = subFun(500, 100); // 也能夠寫成  result = (*subFun)(500, 100);
    printf("result=%d\n", result);
}

這就有點相似java中接口的樣子了。接口

指針函數返回函數指針類型,結合hotspot中實際例子

先是類型定義結合函數指針定義一個CallStub類型,該類型是 一個 指向函數的指針內存

// share/vm/runtime/stubRoutines.hpp
  // Calls to Java SimonNote: 函數指針結合typedef類型定義  最終調用是在 javaCalls中的call_helper()
  typedef void (*CallStub)(
    address   link,
    intptr_t* result,
    BasicType result_type,
    Method* method,
    address   entry_point,
    intptr_t* parameters,
    int       size_of_parameters,
    TRAPS
  );

再定義一個返回函數指針的函數,拆開講:本質是一個函數,是一個返回函數指針的函數,是一個返回 指向函數的指針的 函數,此處還涉及將內存地址直接轉成函數指針(指向函數的指針)string

// share/vm/runtime/stubRoutines.hpp
  // Calls to Java SimonNote: 將內存地址 轉換成函數指針 CAST_TO_FN_PTR   call_stub 就是一個××返回函數指針×× 的 指針函數  ((CallStub)(castable_address(_call_stub_entry)))
  static CallStub call_stub()                              { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }

調用上面定義的io

// share/vm/runtime/javaCalls.cpp
// 發起調用的地方
      StubRoutines::call_stub()(
        (address)&link,
        // (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
        result_val_address,          // see NOTE above (compiler problem)
        result_type,
        method(),
        entry_point,
        args->parameters(),
        args->size_of_parameters(),
        CHECK
      );

直接在內存中寫入機器碼轉成函數指針並調用的demo

下面這段 代碼 還有遺留問題 仍是沒能跑成功,至於緣由,我目前尚未答案。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

typedef int (* addFun)(int a, int b);

int addWrap(int a, int b) {
    int c = add(a, b);
    return c;
}

int add(int a, int b) {
    return a + b;
}

int main() {

    int codeSize = sizeof(char) * 42;
    char* pCode = (char*)malloc(codeSize);
    char* pGen = pCode;
    mprotect(pCode, codeSize, PROT_READ | PROT_WRITE | PROT_EXEC); // 加了這一行也是core。

    *pGen++ = 0x55;  // 0x00000000004004fd <+0>:    55  push   %rbp
    *pGen++ = 0x48; *pGen++ = 0x89; *pGen++ = 0xe5; // 0x00000000004004fe <+1>: 48 89 e5    mov    %rsp,%rbp
    *pGen++ = 0x48; *pGen++ = 0x83; *pGen++ = 0xec; *pGen++ = 0x20; // 0x0000000000400501 <+4>: 48 83 ec 20 sub    $0x20,%rsp
    *pGen++ = 0x89; *pGen++ = 0x7d; *pGen++ = 0xec; // 0x0000000000400505 <+8>: 89 7d ec    mov    %edi,-0x14(%rbp)
    *pGen++ = 0x89; *pGen++ = 0x75; *pGen++ = 0xe8; // 0x0000000000400508 <+11>:    89 75 e8    mov    %esi,-0x18(%rbp)
    *pGen++ = 0x8b; *pGen++ = 0x55; *pGen++ = 0xe8; //     0x000000000040050b <+14>:    8b 55 e8    mov    -0x18(%rbp),%edx
    *pGen++ = 0x8b; *pGen++ = 0x45; *pGen++ = 0xec; //     0x000000000040050e <+17>:    8b 45 ec    mov    -0x14(%rbp),%eax
    *pGen++ = 0x89; *pGen++ = 0xd6; //     0x0000000000400511 <+20>:    89 d6   mov    %edx,%esi
    *pGen++ = 0x89; *pGen++ = 0xc7; //     0x0000000000400513 <+22>:    89 c7   mov    %eax,%edi
    *pGen++ = 0xb8; *pGen++ = 0x00; *pGen++ = 0x00; *pGen++ = 0x00; *pGen++ = 0x00; //     0x0000000000400515 <+24>:    b8 00 00 00 00  mov    $0x0,%eax
    *pGen++ = 0xe8; *pGen++ = 0x08; *pGen++ = 0x00; *pGen++ = 0x00; *pGen++ = 0x00; //     0x000000000040051a <+29>:    e8 08 00 00 00  callq  0x400527 <add>

    *pGen++ = 0x89; *pGen++ = 0x45; *pGen++ = 0xfc; //     0x000000000040051f <+34>:    89 45 fc    mov    %eax,-0x4(%rbp)
    *pGen++ = 0x8b; *pGen++ = 0x45; *pGen++ = 0xfc; //     0x0000000000400522 <+37>:    8b 45 fc    mov    -0x4(%rbp),%eax
    *pGen++ = 0xc9; //     0x0000000000400525 <+40>:    c9  leaveq
    *pGen++ = 0xc3; //     0x0000000000400526 <+41>:    c3  retq

//  addFun af = addWrap;
    addFun af = (addFun)pCode; // 若是我把這行註釋掉  上一行行去掉註釋  用上一行的方式 就沒問題 可是反之 跑起來就會core core在下一行
    int r = af(5, 15);
    printf("%d\n", r);
    free(pCode);
    return 0;
}

有人說用mmap才能夠解決core的問題
我有空試試

相關文章
相關標籤/搜索