mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-16 21:15:18 +03:00
CFI: Fix LTO for #![no_builtins] crates with CFI
Fixes LTO for `#![no_builtins]` crates with CFI enabled by using rustc's `EmitObj::Bitcode` path (and emitting LLVM bitcode in the `.o` for linker-based LTO).
This commit is contained in:
@@ -136,7 +136,8 @@ macro_rules! if_regular {
|
||||
let emit_obj = if !should_emit_obj {
|
||||
EmitObj::None
|
||||
} else if sess.target.obj_is_bitcode
|
||||
|| (sess.opts.cg.linker_plugin_lto.enabled() && !no_builtins)
|
||||
|| (sess.opts.cg.linker_plugin_lto.enabled()
|
||||
&& (!no_builtins || tcx.sess.is_sanitizer_cfi_enabled()))
|
||||
{
|
||||
// This case is selected if the target uses objects as bitcode, or
|
||||
// if linker plugin LTO is enabled. In the linker plugin LTO case
|
||||
@@ -144,14 +145,23 @@ macro_rules! if_regular {
|
||||
// and convert it to object code. This may be done by either the
|
||||
// native linker or rustc itself.
|
||||
//
|
||||
// Note, however, that the linker-plugin-lto requested here is
|
||||
// explicitly ignored for `#![no_builtins]` crates. These crates are
|
||||
// specifically ignored by rustc's LTO passes and wouldn't work if
|
||||
// loaded into the linker. These crates define symbols that LLVM
|
||||
// lowers intrinsics to, and these symbol dependencies aren't known
|
||||
// until after codegen. As a result any crate marked
|
||||
// `#![no_builtins]` is assumed to not participate in LTO and
|
||||
// instead goes on to generate object code.
|
||||
// By default this branch is skipped for `#![no_builtins]` crates so
|
||||
// they emit native object files (machine code), not LLVM bitcode
|
||||
// objects for the linker (see rust-lang/rust#146133).
|
||||
//
|
||||
// However, when LLVM CFI is enabled (`-Zsanitizer=cfi`), this
|
||||
// breaks LLVM's expected pipeline: LLVM emits `llvm.type.test`
|
||||
// intrinsics and related metadata that must be lowered by LLVM's
|
||||
// `LowerTypeTests` pass before instruction selection during
|
||||
// link-time LTO. Otherwise, `llvm.type.test` intrinsics and related
|
||||
// metadata are not lowered by LLVM's `LowerTypeTests` pass before
|
||||
// reaching the target backend, and LLVM may abort during codegen
|
||||
// (for example in SelectionDAG type legalization) (see
|
||||
// rust-lang/rust#142284).
|
||||
//
|
||||
// Therefore, with `-Clinker-plugin-lto` and `-Zsanitizer=cfi`, a
|
||||
// `#![no_builtins]` crate must still use rustc's `EmitObj::Bitcode`
|
||||
// path (and emit LLVM bitcode in the `.o` for linker-based LTO).
|
||||
EmitObj::Bitcode
|
||||
} else if need_bitcode_in_object(tcx) || sess.target.requires_lto {
|
||||
EmitObj::ObjectCode(BitcodeSection::Full)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cfi-types"
|
||||
version = "0.0.8"
|
||||
|
||||
[[package]]
|
||||
name = "cross-lang-cfi-types-crate-abort"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfi-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cross-lang-cfi-types-crate-not-abort"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfi-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indirect-arity-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "indirect-pointee-type-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "indirect-return-type-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "indirect-type-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "indirect-type-qualifier-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "invalid-branch-target-abort"
|
||||
version = "0.1.0"
|
||||
@@ -0,0 +1,13 @@
|
||||
# Workspace mirroring the examples in <https://github.com/rcvalle/rust-cfi-examples>.
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"invalid-branch-target-abort",
|
||||
"indirect-arity-mismatch-abort",
|
||||
"indirect-pointee-type-mismatch-abort",
|
||||
"indirect-return-type-mismatch-abort",
|
||||
"indirect-type-qualifier-mismatch-abort",
|
||||
"indirect-type-mismatch-abort",
|
||||
"cross-lang-cfi-types-crate-abort",
|
||||
"cross-lang-cfi-types-crate-not-abort",
|
||||
]
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "cross-lang-cfi-types-crate-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cfi-types = { path = "../vendor/cfi-types" }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
include!("../shared_build_rs.rs");
|
||||
|
||||
fn main() {
|
||||
build_foo_static_lib(&[]);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
int
|
||||
do_twice(int (*fn)(int), int arg)
|
||||
{
|
||||
return fn(arg) + fn(arg);
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with different return and parameter types than the
|
||||
// return type expected and arguments intended/passed at the call/branch site,
|
||||
// across the FFI boundary using the `cfi_types` crate for cross-language LLVM
|
||||
// CFI.
|
||||
|
||||
use std::mem;
|
||||
|
||||
use cfi_types::{c_int, c_long};
|
||||
|
||||
#[link(name = "foo")]
|
||||
unsafe extern "C" {
|
||||
fn do_twice(f: unsafe extern "C" fn(c_int) -> c_int, arg: i32) -> i32;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn add_one(x: c_int) -> c_int {
|
||||
c_int(x.0 + 1)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn add_two(x: c_long) -> c_long {
|
||||
c_long(x.0 + 2)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = unsafe { do_twice(add_one, 5) };
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: unsafe extern "C" fn(c_int) -> c_int = unsafe {
|
||||
mem::transmute::<*const u8, unsafe extern "C" fn(c_int) -> c_int>(add_two as *const u8)
|
||||
};
|
||||
let next_answer = unsafe { do_twice(f, 5) };
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "cross-lang-cfi-types-crate-not-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cfi-types = { path = "../vendor/cfi-types" }
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
include!("../shared_build_rs.rs");
|
||||
|
||||
fn main() {
|
||||
build_foo_static_lib(&[]);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// This definition has the type id "_ZTSFvlE".
|
||||
void
|
||||
hello_from_c(long arg)
|
||||
{
|
||||
printf("Hello from C!\n");
|
||||
}
|
||||
|
||||
// This definition has the type id "_ZTSFvPFvlElE"--this can be ignored for the
|
||||
// purposes of this example.
|
||||
void
|
||||
indirect_call_from_c(void (*fn)(long), long arg)
|
||||
{
|
||||
// This call site tests whether the destination pointer is a member of the
|
||||
// group derived from the same type id of the fn declaration, which has the
|
||||
// type id "_ZTSFvlE".
|
||||
//
|
||||
// Notice that since the test is at the call site and generated by Clang,
|
||||
// the type id used in the test is encoded by Clang.
|
||||
fn(arg);
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
use cfi_types::c_long;
|
||||
|
||||
#[link(name = "foo")]
|
||||
extern "C" {
|
||||
// This declaration has the type id "_ZTSFvlE" because it uses the CFI types
|
||||
// for cross-language LLVM CFI support. The cfi_types crate provides a new
|
||||
// set of C types as user-defined types using the cfi_encoding attribute and
|
||||
// repr(transparent) to be used for cross-language LLVM CFI support. This
|
||||
// new set of C types allows the Rust compiler to identify and correctly
|
||||
// encode C types in extern "C" function types indirectly called across the
|
||||
// FFI boundary when CFI is enabled.
|
||||
fn hello_from_c(_: c_long);
|
||||
|
||||
// This declaration has the type id "_ZTSFvPFvlElE" because it uses the CFI
|
||||
// types for cross-language LLVM CFI support--this can be ignored for the
|
||||
// purposes of this example.
|
||||
fn indirect_call_from_c(f: unsafe extern "C" fn(c_long), arg: c_long);
|
||||
}
|
||||
|
||||
// This definition has the type id "_ZTSFvlE" because it uses the CFI types for
|
||||
// cross-language LLVM CFI support, similarly to the hello_from_c declaration
|
||||
// above.
|
||||
unsafe extern "C" fn hello_from_rust(_: c_long) {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
// This definition has the type id "_ZTSFvlE" because it uses the CFI types for
|
||||
// cross-language LLVM CFI support, similarly to the hello_from_c declaration
|
||||
// above.
|
||||
unsafe extern "C" fn hello_from_rust_again(_: c_long) {
|
||||
println!("Hello from Rust again!");
|
||||
}
|
||||
|
||||
// This definition would also have the type id "_ZTSFvPFvlElE" because it uses
|
||||
// the CFI types for cross-language LLVM CFI support, similarly to the
|
||||
// hello_from_c declaration above--this can be ignored for the purposes of this
|
||||
// example.
|
||||
fn indirect_call(f: unsafe extern "C" fn(c_long), arg: c_long) {
|
||||
// This indirect call site tests whether the destination pointer is a member
|
||||
// of the group derived from the same type id of the f declaration, which
|
||||
// has the type id "_ZTSFvlE" because it uses the CFI types for
|
||||
// cross-language LLVM CFI support, similarly to the hello_from_c
|
||||
// declaration above.
|
||||
unsafe { f(arg) }
|
||||
}
|
||||
|
||||
// This definition has the type id "_ZTSFvvE"--this can be ignored for the
|
||||
// purposes of this example.
|
||||
fn main() {
|
||||
// This demonstrates an indirect call within Rust-only code using the same
|
||||
// encoding for hello_from_rust and the test at the indirect call site at
|
||||
// indirect_call (i.e., "_ZTSFvlE").
|
||||
indirect_call(hello_from_rust, c_long(5));
|
||||
|
||||
// This demonstrates an indirect call across the FFI boundary with the Rust
|
||||
// compiler and Clang using the same encoding for hello_from_c and the test
|
||||
// at the indirect call site at indirect_call (i.e., "_ZTSFvlE").
|
||||
indirect_call(hello_from_c, c_long(5));
|
||||
|
||||
// This demonstrates an indirect call to a function passed as a callback
|
||||
// across the FFI boundary with the Rust compiler and Clang the same
|
||||
// encoding for the passed-callback declaration and the test at the indirect
|
||||
// call site at indirect_call_from_c (i.e., "_ZTSFvlE").
|
||||
unsafe {
|
||||
indirect_call_from_c(hello_from_rust_again, c_long(5));
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cross-lang-integer-normalization-abort"
|
||||
version = "0.1.0"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "cross-lang-integer-normalization-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Not a member of the parent `sanitizer-cfi-build-std-clang` workspace so it can
|
||||
# be built with different `RUSTFLAGS` (i.e., integer normalization).
|
||||
[workspace]
|
||||
members = ["."]
|
||||
resolver = "2"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
include!("../shared_build_rs.rs");
|
||||
|
||||
fn main() {
|
||||
build_foo_static_lib(&["-fsanitize-cfi-icall-experimental-normalize-integers"]);
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
int
|
||||
do_twice(int (*fn)(int), int arg)
|
||||
{
|
||||
return fn(arg) + fn(arg);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with different return and parameter types than the
|
||||
// return type expected and arguments intended/passed at the call/branch site,
|
||||
// across the FFI boundary using integer normalization for cross-language LLVM
|
||||
// CFI.
|
||||
|
||||
use std::mem;
|
||||
|
||||
#[link(name = "foo")]
|
||||
extern "C" {
|
||||
fn do_twice(f: unsafe extern "C" fn(i32) -> i32, arg: i32) -> i32;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn add_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn add_two(x: i64) -> i64 {
|
||||
x + 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = unsafe { do_twice(add_one, 5) };
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: unsafe extern "C" fn(i32) -> i32 = unsafe {
|
||||
mem::transmute::<*const u8, unsafe extern "C" fn(i32) -> i32>(add_two as *const u8)
|
||||
};
|
||||
let next_answer = unsafe { do_twice(f, 5) };
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cross-lang-integer-normalization-not-abort"
|
||||
version = "0.1.0"
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "cross-lang-integer-normalization-not-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Not a member of the parent `sanitizer-cfi-build-std-clang` workspace so it can
|
||||
# be built with different `RUSTFLAGS` (i.e., integer normalization).
|
||||
[workspace]
|
||||
members = ["."]
|
||||
resolver = "2"
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
include!("../shared_build_rs.rs");
|
||||
|
||||
fn main() {
|
||||
build_foo_static_lib(&["-fsanitize-cfi-icall-experimental-normalize-integers"]);
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// This definition has the type id "_ZTSFvlE".
|
||||
void
|
||||
hello_from_c(long arg)
|
||||
{
|
||||
printf("Hello from C!\n");
|
||||
}
|
||||
|
||||
// This definition has the type id "_ZTSFvPFvlElE"--this can be ignored for the
|
||||
// purposes of this example.
|
||||
void
|
||||
indirect_call_from_c(void (*fn)(long), long arg)
|
||||
{
|
||||
// This call site tests whether the destination pointer is a member of the
|
||||
// group derived from the same type id of the fn declaration, which has the
|
||||
// type id "_ZTSFvlE".
|
||||
//
|
||||
// Notice that since the test is at the call site and generated by Clang,
|
||||
// the type id used in the test is encoded by Clang.
|
||||
fn(arg);
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
use std::ffi::c_long;
|
||||
|
||||
#[link(name = "foo")]
|
||||
extern "C" {
|
||||
// This declaration would have the type id "_ZTSFvlE", but at the time types
|
||||
// are encoded, all type aliases are already resolved to their respective
|
||||
// Rust aliased types, so this is encoded either as "_ZTSFvu3i32E" or
|
||||
// "_ZTSFvu3i64E" depending to what type c_long type alias is resolved to,
|
||||
// which currently uses the u<length><type-name> vendor extended type
|
||||
// encoding for the Rust integer types--this is the problem demonstrated in
|
||||
// this example.
|
||||
fn hello_from_c(_: c_long);
|
||||
|
||||
// This declaration would have the type id "_ZTSFvPFvlElE", but is encoded
|
||||
// either as "_ZTSFvPFvu3i32ES_E" (compressed) or "_ZTSFvPFvu3i64ES_E"
|
||||
// (compressed), similarly to the hello_from_c declaration above--this can
|
||||
// be ignored for the purposes of this example.
|
||||
fn indirect_call_from_c(f: unsafe extern "C" fn(c_long), arg: c_long);
|
||||
}
|
||||
|
||||
// This definition would have the type id "_ZTSFvlE", but is encoded either as
|
||||
// "_ZTSFvu3i32E" or "_ZTSFvu3i64E", similarly to the hello_from_c declaration
|
||||
// above.
|
||||
unsafe extern "C" fn hello_from_rust(_: c_long) {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
// This definition would have the type id "_ZTSFvlE", but is encoded either as
|
||||
// "_ZTSFvu3i32E" or "_ZTSFvu3i64E", similarly to the hello_from_c declaration
|
||||
// above.
|
||||
unsafe extern "C" fn hello_from_rust_again(_: c_long) {
|
||||
println!("Hello from Rust again!");
|
||||
}
|
||||
|
||||
// This definition would also have the type id "_ZTSFvPFvlElE", but is encoded
|
||||
// either as "_ZTSFvPFvu3i32ES_E" (compressed) or "_ZTSFvPFvu3i64ES_E"
|
||||
// (compressed), similarly to the hello_from_c declaration above--this can be
|
||||
// ignored for the purposes of this example.
|
||||
fn indirect_call(f: unsafe extern "C" fn(c_long), arg: c_long) {
|
||||
// This indirect call site tests whether the destination pointer is a member
|
||||
// of the group derived from the same type id of the f declaration, which
|
||||
// would have the type id "_ZTSFvlE", but is encoded either as
|
||||
// "_ZTSFvu3i32E" or "_ZTSFvu3i64E", similarly to the hello_from_c
|
||||
// declaration above.
|
||||
//
|
||||
// Notice that since the test is at the call site and generated by the Rust
|
||||
// compiler, the type id used in the test is encoded by the Rust compiler.
|
||||
unsafe { f(arg) }
|
||||
}
|
||||
|
||||
// This definition has the type id "_ZTSFvvE"--this can be ignored for the
|
||||
// purposes of this example.
|
||||
fn main() {
|
||||
// This demonstrates an indirect call within Rust-only code using the same
|
||||
// encoding for hello_from_rust and the test at the indirect call site at
|
||||
// indirect_call (i.e., "_ZTSFvu3i32E" or "_ZTSFvu3i64E").
|
||||
indirect_call(hello_from_rust, 5);
|
||||
|
||||
// This demonstrates an indirect call across the FFI boundary with the Rust
|
||||
// compiler and Clang using different encodings for hello_from_c and the
|
||||
// test at the indirect call site at indirect_call (i.e., "_ZTSFvu3i32E" or
|
||||
// "_ZTSFvu3i64E" vs "_ZTSFvlE").
|
||||
//
|
||||
// When using rustc LTO (i.e., -Clto), this works because the type id used
|
||||
// is from the Rust-declared hello_from_c, which is encoded by the Rust
|
||||
// compiler (i.e., "_ZTSFvu3i32E" or "_ZTSFvu3i64E").
|
||||
//
|
||||
// When using (proper) LTO (i.e., -Clinker-plugin-lto), this does not work
|
||||
// because the type id used is from the C-defined hello_from_c, which is
|
||||
// encoded by Clang (i.e., "_ZTSFvlE").
|
||||
indirect_call(hello_from_c, 5);
|
||||
|
||||
// This demonstrates an indirect call to a function passed as a callback
|
||||
// across the FFI boundary with the Rust compiler and Clang using different
|
||||
// encodings for the passed-callback declaration and the test at the
|
||||
// indirect call site at indirect_call_from_c (i.e., "_ZTSFvu3i32E" or
|
||||
// "_ZTSFvu3i64E" vs "_ZTSFvlE").
|
||||
//
|
||||
// When Rust functions are passed as callbacks across the FFI boundary to be
|
||||
// called back from C code, the tests are also at the call site but
|
||||
// generated by Clang instead, so the type ids used in the tests are encoded
|
||||
// by Clang, which will not match the type ids of declarations encoded by
|
||||
// the Rust compiler (e.g., hello_from_rust_again). (The same happens the
|
||||
// other way around for C functions passed as callbacks across the FFI
|
||||
// boundary to be called back from Rust code.)
|
||||
unsafe {
|
||||
indirect_call_from_c(hello_from_rust_again, 5);
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "indirect-arity-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with a different number of parameters than
|
||||
// arguments intended/passed at the call/branch site.
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn add_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
|
||||
fn add_two(x: i32, _y: i32) -> i32 {
|
||||
x + 2
|
||||
}
|
||||
|
||||
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
|
||||
f(arg) + f(arg)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = do_twice(add_one, 5);
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: fn(i32) -> i32 =
|
||||
unsafe { mem::transmute::<*const u8, fn(i32) -> i32>(add_two as *const u8) };
|
||||
let next_answer = do_twice(f, 5);
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "indirect-pointee-type-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with different return and parameter (i.e., pointee)
|
||||
// types than the return type expected and arguments intended/passed at the
|
||||
// call/branch site.
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn add_one(x: *const i32) -> i32 {
|
||||
unsafe { *x + 1 }
|
||||
}
|
||||
|
||||
fn add_two(x: *const i64) -> i32 {
|
||||
unsafe { (*x + 2) as i32 }
|
||||
}
|
||||
|
||||
fn do_twice(f: fn(*const i32) -> i32, arg: *const i32) -> i32 {
|
||||
f(arg) + f(arg)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let value: i32 = 5;
|
||||
let answer = do_twice(add_one, &value);
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: fn(*const i32) -> i32 =
|
||||
unsafe { mem::transmute::<*const u8, fn(*const i32) -> i32>(add_two as *const u8) };
|
||||
let next_answer = do_twice(f, &value);
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "indirect-return-type-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with a different return type than the return type
|
||||
// expected at the call/branch site.
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn add_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
|
||||
fn add_two(x: i32) -> i64 {
|
||||
i64::from(x + 2)
|
||||
}
|
||||
|
||||
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
|
||||
f(arg) + f(arg)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = do_twice(add_one, 5);
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: fn(i32) -> i32 =
|
||||
unsafe { mem::transmute::<*const u8, fn(i32) -> i32>(add_two as *const u8) };
|
||||
let next_answer = do_twice(f, 5);
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "indirect-type-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with different return and parameter types than the
|
||||
// return type expected and arguments intended/passed at the call/branch site.
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn add_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
|
||||
fn add_two(x: i64) -> i64 {
|
||||
x + 2
|
||||
}
|
||||
|
||||
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
|
||||
f(arg) + f(arg)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = do_twice(add_one, 5);
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: fn(i32) -> i32 =
|
||||
unsafe { mem::transmute::<*const u8, fn(i32) -> i32>(add_two as *const u8) };
|
||||
let next_answer = do_twice(f, 5);
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "indirect-type-qualifier-mismatch-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to a function with parameter type qualifiers than the argument
|
||||
// type qualifiers intended/passed at the call/branch site.
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn add_one(x: &i32) -> i32 {
|
||||
*x + 1
|
||||
}
|
||||
|
||||
fn add_two(x: &mut i32) -> i32 {
|
||||
*x + 2
|
||||
}
|
||||
|
||||
fn do_twice(f: fn(&i32) -> i32, arg: &i32) -> i32 {
|
||||
f(arg) + f(arg)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let value: i32 = 5;
|
||||
let answer = do_twice(add_one, &value);
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: fn(&i32) -> i32 =
|
||||
unsafe { mem::transmute::<*const u8, fn(&i32) -> i32>(add_two as *const u8) };
|
||||
let next_answer = do_twice(f, &value);
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
[package]
|
||||
name = "invalid-branch-target-abort"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// This example demonstrates redirecting control flow using an indirect
|
||||
// branch/call to an invalid destination (i.e., within the body of the
|
||||
// function).
|
||||
|
||||
use std::mem;
|
||||
|
||||
fn add_one(x: i32) -> i32 {
|
||||
x + 1
|
||||
}
|
||||
|
||||
#[unsafe(naked)]
|
||||
pub extern "C" fn add_two(_x: i32) -> ! {
|
||||
// x + 2 preceded by a landing pad/nop block
|
||||
core::arch::naked_asm!(
|
||||
r#"
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
lea eax, [rdi + 2]
|
||||
ret
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
|
||||
f(arg) + f(arg)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let answer = do_twice(add_one, 5);
|
||||
|
||||
println!("The answer is: {}", answer);
|
||||
|
||||
println!("With CFI enabled, you should not see the next answer");
|
||||
let f: fn(i32) -> i32 = unsafe {
|
||||
// Offset 0 is a valid branch/call destination (i.e., the function entry
|
||||
// point), but offsets 1-8 within the landing pad/nop block are invalid
|
||||
// branch/call destinations (i.e., within the body of the function).
|
||||
mem::transmute::<*const u8, fn(i32) -> i32>((add_two as *const u8).offset(5))
|
||||
};
|
||||
let next_answer = do_twice(f, 5);
|
||||
|
||||
println!("The next answer is: {}", next_answer);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//! Verifies that the examples in <https://github.com/rcvalle/rust-cfi-examples> build and run with
|
||||
//!`-Zbuild-std` to prevent regressions such as [rust-lang/rust#142284].
|
||||
|
||||
//@ needs-sanitizer-cfi
|
||||
//@ needs-force-clang-based-tests
|
||||
//@ needs-rust-lld
|
||||
//@ needs-target-std
|
||||
//@ ignore-cross-compile
|
||||
//@ only-x86_64-unknown-linux-gnu
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use run_make_support::external_deps::rustc::sysroot as rustc_sysroot;
|
||||
use run_make_support::run::cmd;
|
||||
use run_make_support::{bin_name, cargo, path, target};
|
||||
|
||||
fn clang_path() -> String {
|
||||
if let Ok(d) = std::env::var("LLVM_BIN_DIR") {
|
||||
let clang = Path::new(d.trim_end_matches('/')).join("clang");
|
||||
if clang.exists() {
|
||||
return clang.display().to_string();
|
||||
}
|
||||
}
|
||||
if let Ok(clang) = std::env::var("CLANG") {
|
||||
let clang = Path::new(clang.trim_end_matches('/'));
|
||||
if clang.exists() {
|
||||
return clang.display().to_string();
|
||||
}
|
||||
}
|
||||
"clang".to_string()
|
||||
}
|
||||
|
||||
fn fuse_ld_path() -> String {
|
||||
if let Ok(d) = std::env::var("LLVM_BIN_DIR") {
|
||||
let llvm_bin_dir = Path::new(d.trim_end_matches('/'));
|
||||
let gcc_ld_lld = llvm_bin_dir.join("gcc-ld").join("ld.lld");
|
||||
if gcc_ld_lld.exists() {
|
||||
return gcc_ld_lld.display().to_string();
|
||||
}
|
||||
let ld_lld = llvm_bin_dir.join("ld.lld");
|
||||
if ld_lld.exists() {
|
||||
return ld_lld.display().to_string();
|
||||
}
|
||||
}
|
||||
if let Ok(clang) = std::env::var("CLANG") {
|
||||
let clang = Path::new(clang.trim_end_matches('/'));
|
||||
if let Some(clang_dir) = clang.parent() {
|
||||
let gcc_ld_lld = clang_dir.join("gcc-ld").join("ld.lld");
|
||||
if gcc_ld_lld.exists() {
|
||||
return gcc_ld_lld.display().to_string();
|
||||
}
|
||||
let ld_lld = clang_dir.join("ld.lld");
|
||||
if ld_lld.exists() {
|
||||
return ld_lld.display().to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
let target_bin_dir = rustc_sysroot().join("lib").join("rustlib").join(target()).join("bin");
|
||||
let gcc_ld_lld = target_bin_dir.join("gcc-ld").join("ld.lld");
|
||||
if gcc_ld_lld.exists() {
|
||||
return gcc_ld_lld.display().to_string();
|
||||
}
|
||||
"ld.lld".to_string()
|
||||
}
|
||||
|
||||
fn run_and_expect_cfi_abort(target_dir: &Path, target: &str, binary: &str) {
|
||||
let exe = target_dir.join(target).join("release").join(bin_name(binary));
|
||||
let output = cmd(&exe).run_fail();
|
||||
output
|
||||
.assert_stdout_contains("With CFI enabled, you should not see the next answer")
|
||||
.assert_stdout_not_contains("The next answer is:");
|
||||
}
|
||||
|
||||
fn run_and_expect_cfi_not_abort(target_dir: &Path, target: &str, binary: &str) {
|
||||
let exe = target_dir.join(target).join("release").join(bin_name(binary));
|
||||
let output = cmd(&exe).run();
|
||||
output.assert_stdout_contains("Hello from C!");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let clang = clang_path();
|
||||
let fuse_ld = fuse_ld_path();
|
||||
let tgt = target();
|
||||
let target_dir = path("target");
|
||||
let lib = std::env::var("LIB").unwrap_or_default();
|
||||
|
||||
let prior_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default();
|
||||
|
||||
let rustflags = format!(
|
||||
"{prior_rustflags} -Clinker-plugin-lto -Clinker={clang} \
|
||||
-Clink-arg=-fuse-ld={fuse_ld} -Zsanitizer=cfi \
|
||||
-Ctarget-feature=-crt-static"
|
||||
)
|
||||
.trim()
|
||||
.to_owned();
|
||||
|
||||
let rustflags_with_integer_normalization =
|
||||
format!("{rustflags} -Zsanitizer-cfi-normalize-integers").trim().to_owned();
|
||||
|
||||
let run = |manifest: &Path, rustflags: &str, workspace: bool| {
|
||||
let mut c = cargo();
|
||||
c.arg("build")
|
||||
.arg("--manifest-path")
|
||||
.arg(manifest)
|
||||
.arg("--release")
|
||||
.arg("-Zbuild-std")
|
||||
.arg("--target")
|
||||
.arg(&tgt);
|
||||
if workspace {
|
||||
c.arg("--workspace");
|
||||
}
|
||||
c.env("RUSTFLAGS", rustflags)
|
||||
.env("CC", &clang)
|
||||
.env("CARGO_TARGET_DIR", &target_dir)
|
||||
.env("RUSTC_BOOTSTRAP", "1")
|
||||
.env("LIB", &lib)
|
||||
.run();
|
||||
};
|
||||
|
||||
run(Path::new("Cargo.toml"), &rustflags, true);
|
||||
for bin in [
|
||||
"invalid-branch-target-abort",
|
||||
"indirect-arity-mismatch-abort",
|
||||
"indirect-pointee-type-mismatch-abort",
|
||||
"indirect-return-type-mismatch-abort",
|
||||
"indirect-type-qualifier-mismatch-abort",
|
||||
"indirect-type-mismatch-abort",
|
||||
"cross-lang-cfi-types-crate-abort",
|
||||
] {
|
||||
run_and_expect_cfi_abort(&target_dir, &tgt, bin);
|
||||
}
|
||||
for bin in ["cross-lang-cfi-types-crate-not-abort"] {
|
||||
run_and_expect_cfi_not_abort(&target_dir, &tgt, bin);
|
||||
}
|
||||
|
||||
run(
|
||||
Path::new("cross-lang-integer-normalization-abort/Cargo.toml"),
|
||||
&rustflags_with_integer_normalization,
|
||||
false,
|
||||
);
|
||||
run_and_expect_cfi_abort(&target_dir, &tgt, "cross-lang-integer-normalization-abort");
|
||||
|
||||
run(
|
||||
Path::new("cross-lang-integer-normalization-not-abort/Cargo.toml"),
|
||||
&rustflags_with_integer_normalization,
|
||||
false,
|
||||
);
|
||||
run_and_expect_cfi_not_abort(&target_dir, &tgt, "cross-lang-integer-normalization-not-abort");
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn clang_path() -> PathBuf {
|
||||
if let Ok(d) = env::var("LLVM_BIN_DIR") {
|
||||
let clang = Path::new(d.trim_end_matches('/')).join("clang");
|
||||
if clang.exists() {
|
||||
return clang;
|
||||
}
|
||||
}
|
||||
if let Ok(clang) = env::var("CLANG") {
|
||||
let clang = Path::new(clang.trim_end_matches('/'));
|
||||
if clang.exists() {
|
||||
return clang.to_path_buf();
|
||||
}
|
||||
}
|
||||
PathBuf::from("clang")
|
||||
}
|
||||
|
||||
fn llvm_ar_path() -> PathBuf {
|
||||
if let Ok(d) = env::var("LLVM_BIN_DIR") {
|
||||
let llvm_ar = Path::new(d.trim_end_matches('/')).join("llvm-ar");
|
||||
if llvm_ar.exists() {
|
||||
return llvm_ar;
|
||||
}
|
||||
}
|
||||
if let Ok(clang) = env::var("CLANG") {
|
||||
let clang = Path::new(&clang);
|
||||
if let Some(clang_dir) = clang.parent() {
|
||||
let llvm_ar = clang_dir.join("llvm-ar");
|
||||
if llvm_ar.exists() {
|
||||
return llvm_ar;
|
||||
}
|
||||
}
|
||||
}
|
||||
PathBuf::from("llvm-ar")
|
||||
}
|
||||
|
||||
fn build_foo_static_lib(extra_flags: &[&str]) {
|
||||
let out_dir = env::var("OUT_DIR").expect("OUT_DIR");
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
|
||||
let c_src = Path::new(&manifest_dir).join("src/foo.c");
|
||||
let bc_path = Path::new(&out_dir).join("foo.bc");
|
||||
let a_path = Path::new(&out_dir).join("libfoo.a");
|
||||
|
||||
let clang = clang_path();
|
||||
let llvm_ar = llvm_ar_path();
|
||||
|
||||
let mut clang_args = vec!["-Wall", "-flto=thin", "-fsanitize=cfi"];
|
||||
clang_args.extend_from_slice(extra_flags);
|
||||
clang_args.extend_from_slice(&["-fvisibility=hidden", "-c", "-emit-llvm", "-o"]);
|
||||
|
||||
let st = Command::new(&clang)
|
||||
.args(&clang_args)
|
||||
.arg(&bc_path)
|
||||
.arg(&c_src)
|
||||
.status()
|
||||
.unwrap_or_else(|e| panic!("failed to spawn `{}`: {e}", clang.display()));
|
||||
assert!(st.success(), "`{}` failed with {st}", clang.display());
|
||||
|
||||
let st = Command::new(&llvm_ar)
|
||||
.args(["rcs", a_path.to_str().unwrap(), bc_path.to_str().unwrap()])
|
||||
.status()
|
||||
.unwrap_or_else(|e| panic!("failed to spawn `{}`: {e}", llvm_ar.display()));
|
||||
assert!(st.success(), "`{}` failed with {st}", llvm_ar.display());
|
||||
|
||||
println!("cargo:rustc-link-search=native={out_dir}");
|
||||
println!("cargo:rustc-link-lib=static=foo");
|
||||
println!("cargo:rerun-if-changed={}", c_src.display());
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=../shared_build_rs.rs");
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "cfi-types"
|
||||
version = "0.0.8"
|
||||
edition = "2021"
|
||||
|
||||
description = "CFI types for cross-language LLVM CFI support"
|
||||
homepage = "https://github.com/rcvalle/rust-crate-cfi-types"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/rcvalle/rust-crate-cfi-types"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
//! CFI types for cross-language LLVM CFI support.
|
||||
//!
|
||||
//! The cfi_types crate provides a new set of C types as user-defined types
|
||||
//! using the cfi_encoding attribute and repr(transparent) to be used for
|
||||
//! cross-language LLVM CFI support. This new set of C types allows the Rust
|
||||
//! compiler to identify and correctly encode C types in extern "C" function
|
||||
//! types indirectly called across the FFI boundary when CFI is enabled.
|
||||
//!
|
||||
//! The use of these types are optional and are recommended for when enforcement
|
||||
//! and explicitness of types used across the FFI boundary and no loss of
|
||||
//! granularity for cross-language LLVM CFI are preferred.
|
||||
//!
|
||||
//! Alternatively, the `-Zsanitizer-cfi-normalize-integers` option may be used
|
||||
//! with the Clang `-fsanitize-cfi-icall-experimental-normalize-integers` option
|
||||
//! for cross-language LLVM CFI support.
|
||||
|
||||
#![feature(cfg_sanitizer_cfi)]
|
||||
#![feature(cfi_encoding)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_char type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "c")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_char(pub core::ffi::c_char);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_int type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "i")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_int(pub core::ffi::c_int);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_long type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "l")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_long(pub core::ffi::c_long);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_longlong type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "x")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_longlong(pub core::ffi::c_longlong);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_schar type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "a")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_schar(pub core::ffi::c_schar);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_short type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "s")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_short(pub core::ffi::c_short);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_uchar type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "h")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_uchar(pub core::ffi::c_uchar);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_uint type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "j")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_uint(pub core::ffi::c_uint);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_ulong type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "m")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_ulong(pub core::ffi::c_ulong);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_ulonglong type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "y")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_ulonglong(pub core::ffi::c_ulonglong);
|
||||
|
||||
/// CFI type equivalent to Rust's core::ffi::c_ushort type alias.
|
||||
#[allow(dead_code)]
|
||||
#[cfg_attr(not(sanitizer_cfi_normalize_integers), cfi_encoding = "t")]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct c_ushort(pub core::ffi::c_ushort);
|
||||
Reference in New Issue
Block a user