Rust 获取函数地址 裸指针

研究性代码

alert('doo');

#[no_mangle]
// extern "C" fn using(f: extern "C" fn()) {
extern "C" fn using(f: *const u8) {
    let a = (&f) as *const _ as *const fn();
    let b = unsafe { *a };
    println!("DLL开始");
    b();
    println!("DLL结束");
}

extern "C" fn a() {
    println!("a()...");
}

#[no_mangle]
extern "C" fn DllMain(hinstDLL: *mut u8, reason: u32, reserved: *mut u8) -> u32 {
    match reason {
        1 => {
            // DLL_PROCESS_ATTACH
            println!("连接到进程!");
            intro();
        }
        0 => {
            // DLL_PROCESS_DETACH
            println!("检测到进程退出");
        }
        _ => (),
    }
    return 1;
}

fn intro() {
    let p = a as extern "C" fn ();
    println!("p -> {:p}.", p); // 该地址正确,不过类型是fn()

    let p = &a as *const _ as *const u8;
    println!("p -> {:p}.", p); // 似乎是临时变量的地址

    let p = &(a as extern "C" fn ()) as *const _ as *const *mut u8;
    println!("p -> {:p}.", p); // 这个肯定是临时变量的地址,因为解引用之后正是a函数的地址

    println!("p -> {:p}.", unsafe { *p }); // a函数正确的地址
    unsafe {
        using(*p); // 该函数接收的是一个C函数的地址
    }
}

一般结论

函数其实是一个结构体,除了代码地址以外还附带其它一些属性。(更新:这句话说错了)

一般不建议直接使用裸指针,如果要传递一个函数指针给C,可以改写:

#[link(name = "my_c_program", kind = "dylib")]
extern "C" {
    fn SetCallback(callback: *const u8);
}
// 改写为:
#[link(name = "my_c_program", kind = "dylib")]
extern "C" {
    fn SetCallback(callback: fn(input: i32) -> i32);
}
// 就可以直接使用函数名或者闭包传递参数给C了:
SetCallback(main);
SetCallback(| input | -> i64 {
    return 0;
});

当然,如果非要使用裸指针,那也是可以的:
虽然fn()对象不可以直接强转为裸指针,不过我们可以使用一个双重指针,unsafe解引用就可以了:

type HookProc = fn(nCode: i32, wParam: u64, lParam: *const u8) -> i64; // 函数类型
// 首先将函数指针保存在另一个指针变量中:
// 至于这两种的区别,我们稍后研究
let ppfn = &callback as *const _ as *const *const u8; // 这种做法通常是错误的,只有闭包可以这么使用
let ppfn = &(callback as HookProc) as *const _ as *const *const u8;
// 然后解引用:
let pfn: *const u8 = unsafe { *ppfn };
SetCallback(pfn);

callback、&callback、&(callback as HookProc) 的区别

// 由于Rust函数具有可重载等特性,我们用一个静态闭包代替函数
static callback: fn() = || {
    println!("函数运行...\n");
};

#[inline(always)]
fn a() {
    let task = std::thread::spawn(|| unsafe {
        let ppfn = &callback as *const _ as *const fn();
        println!("&callback -> {:p}", ppfn);
        println!("解引用&callback -> {:p}", *ppfn);
        (*ppfn)();
    });
    task.join().unwrap();
}

#[inline(always)]
fn b() {
    let task = std::thread::spawn(|| unsafe {
        let ppfn = &(callback as fn()) as *const _ as *const fn();
        println!("&(callback as fn()) -> {:p}", ppfn);
        println!("解引用&(callback as fn()) -> {:p}", *ppfn);
        (*ppfn)();
    });
    task.join().unwrap();
}

#[test]
fn it_works() {
    println!("我们所需要的裸指针 -> {:p} \n", callback); // 如果callback不是闭包,这里将编译失败

    a();
    a();

    b();
    b();
}

程序输出:

我们所需要的裸指针 -> 0x7ff6c3d124f0 

&callback -> 0x7ff6c3d96998
解引用&callback -> 0x7ff6c3d124f0
函数运行...

&callback -> 0x7ff6c3d96998   <-----此值保持不变,可能是静态区数据,或者是堆上的数据
解引用&callback -> 0x7ff6c3d124f0
函数运行...

&(callback as fn()) -> 0x162a8ff468
解引用&(callback as fn()) -> 0x7ff6c3d124f0
函数运行...

&(callback as fn()) -> 0x162a8ffa18   <-----此值发生了变化,即使去掉多线程,与上一次的值相差也是很大,不只一个指针,说明这个结构体不小
解引用&(callback as fn()) -> 0x7ff6c3d124f0
函数运行...

我暂时将&(callback as fn())理解为等价于以下形式:

#[inline(always)]
fn c() {
    let task = std::thread::spawn(|| unsafe {
        let inner_var = callback as fn();   <-----fn()对象克隆给了一个中间变量
        let ppfn = &(inner_var) as *const _ as *const fn();
        println!("&(callback as fn()) -> {:p}", ppfn);
        println!("解引用&(callback as fn()) -> {:p}", *ppfn);
        (*ppfn)();
    });
    task.join().unwrap();
}

好了,现在把callback改回函数,完蛋了,a()运行失败:

&callback -> 0x7ff64fee7598
解引用&callback -> 0x6361626c6c616326
process didn't exit successfully: `xx.exe it_works --exact --nocapture` (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

而且发现b和c也不完全等价,因为指针差的有点远:

&(callback as fn()) -> 0x7ff79b0b6598
解引用&(callback as fn()) -> 0x7ff79b0372a0
函数运行...

&(callback as fn()) -> 0x7ff79b0b6598
解引用&(callback as fn()) -> 0x7ff79b0372a0
函数运行...

&(inner_var) -> 0x296d4ff760
解引用&(inner_var) -> 0x7ff79b0372a0
函数运行...

&(inner_var) -> 0x296d4ff400
解引用&(inner_var) -> 0x7ff79b0372a0
函数运行...

源码在这里:

fn callback() {
    println!("函数运行...\n");
}

#[inline(always)]
fn b() {
    let task = std::thread::spawn(|| unsafe {
        let ppfn = &(callback as fn()) as *const _ as *const fn();
        println!("&(callback as fn()) -> {:p}", ppfn);
        println!("解引用&(callback as fn()) -> {:p}", *ppfn);
        (*ppfn)();
    });
    task.join().unwrap();
}

#[inline(always)]
fn c() {
    let task = std::thread::spawn(|| unsafe {
        let inner_var = callback as fn();
        let ppfn = &(inner_var) as *const _ as *const fn();
        println!("&(inner_var) -> {:p}", ppfn);
        println!("解引用&(inner_var) -> {:p}", *ppfn);
        (*ppfn)();
    });
    task.join().unwrap();
}

#[test]
fn it_works() {
    // println!("我们所需要的裸指针 -> {:p} \n", callback); // 编译失败
    // a(); // 运行失败

    b();
    b();

    c();
    c();
}

结论

使用以下方式获取Rust函数原始指针:

let ppfn = &(callback as fn()) as *const _ as *const fn();
let pfn = unsafe { *ppfn };

更新

let pfn: *const u8 = callback as *const _; // 函数可以转为任意类型的指针

// 指针转为函数则比较麻烦,虽然它们其实具有相同的值,不过编译器认为类型完全不一样,所以需要临时变量
let f: fn() = *(&pfn as *const _ as *const fn());

// 下面这种写法看起来帅,但严格意义上来说是不正确的,你不能对pfn解引用去得到一个fn变量,因为*callback就是代码区了,callback就是fn
let pfn = callback as *const fn();

不过最好还是不要使用指针:

use std::os::raw::c_int;

#[link(name = "libtest", kind = "dylib")]
extern {
    fn call_raw_fn(proc: extern fn(c_int, c_int) -> c_int);
}

fn main() {
    unsafe {
        call_raw_fn(foo);
    }
}

extern fn foo(a: c_int, b: c_int) -> c_int {
    a + b
}

END

posted @ 2022-03-11 18:31  develon  阅读(1152)  评论(0编辑  收藏  举报