rust-lang / rust-bindgen

Automatically generates Rust FFI bindings to C (and some C++) libraries.
https://rust-lang.github.io/rust-bindgen/
BSD 3-Clause "New" or "Revised" License
4.29k stars 682 forks source link

default ABI #2257

Closed kcrazy closed 1 year ago

kcrazy commented 1 year ago

the default ABI of the windows kernel is stdcall, so the most functions do not specify ABI. Is it possible to increase the default ABI option? example default_abi(name: &str)

bindgen::Builder::default().default_abi("stdcall"). ...;

Input C/C++ Header

// #include <ntifs.h>

#define DECLSPEC_IMPORT __declspec(dllimport)
#define NTKERNELAPI DECLSPEC_IMPORT     // wdm ntndis ntifs

NTKERNELAPI
VOID
IoDeleteDevice (
    PDEVICE_OBJECT DeviceObject
    );

Bindgen Invocation

bindgen::Builder::default()
        .header("wrapper/wrapper.h")
        .use_core()
        .derive_debug(false)
        .layout_tests(false)
        .ctypes_prefix("cty")
        .default_enum_style(bindgen::EnumVariation::ModuleConsts)
        .clang_arg(format!("-I{}", include_dir.to_str().unwrap()))
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .blocklist_type(".*")
        .allowlist_function(".*")
        .allowlist_recursively(false)
        .generate()
        .unwrap();

Actual Results

extern "C" {
    pub fn IoDeleteDevice(DeviceObject: PDEVICE_OBJECT);
}

Expected Results

extern "stdcall" {
    pub fn IoDeleteDevice(DeviceObject: PDEVICE_OBJECT);
}
pvdrz commented 1 year ago

I dug a bit around this issue and couldn't figure out when clang actually decides to use the Default calling convention. Every example I tried in linux and windows had the C calling convention and there was no way to distinguish that from functions tagged with the cdecl attribute for example.

I guess one alternative would be to do this in postprocessing (or even inside codegen I guess) and add a new family of regex options to bindgen so we can set the ABI to whatever we want but I'm not sure that's the right approach as it would change the calling convention arbitrarily without respecting the target or the header inputs.

Any ideas @emilio, @kulp, @amanjeev?

emilio commented 1 year ago

Yeah, so right now we make the assumption that the default ABI is "C": https://github.com/rust-lang/rust-bindgen/blob/63bf6433e10156899e614c73d03ada682ff079ac/bindgen/ir/function.rs#L243

Ideally we'd just do the right thing based on the target. If clang exposes the resolved "default" ABI that'd be preferrable to adding a new option.

pvdrz commented 1 year ago

But AFAIK CXCallingConv_Default is not actually returned anywhere by libclang.

Edit: Just wanted to make it clear that I changed exactly that line expecting it would work but it does not :(. Apparently what happens is that this CXCallingConv_Default variant is defined by clang but not returned anywhere at all so everything is tagged as using the C calling convention.

pvdrz commented 1 year ago

Yep, if you check libclang's code, you'll notice that clang_getFunctionTypeCallingConv actually calls getCallConv, which at the same time calls getCC, this last function returns a CallingConv value (this enum doesn't even have a "default calling convention" variant). This value is built from a Bits field that by default sets the calling convention to CC_C.

So no idea where this CXCallingConv_Default value is used and how.

As a consequence. I think there's no way to distingush a function without any calling convention attributes and one with the cdecl calling convention attribute.

pvdrz commented 1 year ago

Given that it seems there's no way to override the default calling convention without changing items that explicitly use the C calling convention I wonder if we could add a regex option like this:

builder.override_abi("matching_function", ABI::Stdcall);

so this input:

void matching_function();
void other_function();

emits this output:

extern "stdcall" {
    fn matching_function();
}
extern "C" {
    fn other_function();
}

This would also fix https://github.com/rust-lang/rust-bindgen/issues/2224

mmotejlek commented 1 year ago

Hi, I've been expermenting with Windows driver APIs and Bindgen and stumbled into this thread.

I'm not sure if it's relevant but here are my observations. I'm very new to this so please excuse me if I'm stating the obvious or doing something wrong.

The wrapper file:

#include <10.0.22621.0/um/windows.h>

#include <wdf/umdf/2.33/wdf.h>

// Needs to be compiled as C++
#define IDD_STUB
#include <10.0.22621.0/um/iddcx/1.9/IddCx.h>

and relevant snippets from build.rs (commented lines are variants):

println!("cargo:rustc-link-search=C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/um/x64/iddcx/1.9");

// println!("cargo:rustc-link-search=C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/um/x86/iddcx/1.9");

println!("cargo:rustc-link-lib=iddcxstub");

bindgen::Builder::default()
        // .clang_arg("--target=i686-pc-windows-msvc")
        // .clang_arg("-mrtd")
        .clang_arg("--language=c++")
        .clang_arg("--include-directory=C:/Program Files (x86)/Windows Kits/10/Include")
        .header("wrapper.hpp")
        .allowlist_type("DRIVER_INITIALIZE")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks))
        .use_core()
        .generate()

As far as I understand, compiling for x64 doesn't differentiate between cdecl and stdcall because they are converted to a different calling convention. So if I understand correctly, I should be able to use extern "C" against Windows API no matter if it uses cdecl or stdcall as long as I'm compiling for x64?

Also regarding:

the default ABI of the windows kernel is stdcall, so the most functions do not specify ABI.

I'd like to ask where one learns this kind of info because searching for a confirmation of this is what lead me here in the first place.