• 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 };
    

    END

  • 相关阅读:
    (三)RabbitMQ:客户端开发向导
    (二)RabbitMQ:RabbitMQ相关概念介绍
    (一)RabbitMQ:RabbitMQ初体验
    支付宝手机网站支付接入(沙箱环境)
    vue-cli3.0 的使用
    Windows平台上好用的SSH客户端
    JWT
    cnetos 安装nginx后查找nginx配置文件路径
    centos搭建nginx服务,给某个目录及子目录nginx的读取权限
    windows下mysqldump.exe、mysql.exe导出mysql数据到sql文件
  • 原文地址:https://www.cnblogs.com/develon/p/15994912.html
Copyright © 2020-2023  润新知