From eef93b932844110cf19a4b2741b528623b77998e Mon Sep 17 00:00:00 2001 From: Josh Ferrell Date: Mon, 1 Sep 2025 17:47:08 -0400 Subject: Add option to build rust api without linking to core --- CMakeLists.txt | 29 +++------ Cargo.lock | 14 ++++- cmake/generate_stubs.py | 121 ------------------------------------ rust/Cargo.toml | 6 +- rust/binaryninjacore-sys/Cargo.toml | 9 ++- rust/binaryninjacore-sys/build.rs | 25 +++++++- rust/build.rs | 6 +- stubs/CMakeLists.txt | 36 +++++++++++ stubs/generate_stubs.py | 121 ++++++++++++++++++++++++++++++++++++ 9 files changed, 216 insertions(+), 151 deletions(-) delete mode 100644 cmake/generate_stubs.py create mode 100644 stubs/CMakeLists.txt create mode 100644 stubs/generate_stubs.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 904fcf4f..3a28b964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,33 +39,22 @@ if(BinaryNinjaCore_FOUND) target_link_directories(binaryninjaapi PUBLIC ${BinaryNinjaCore_LIBRARY_DIRS}) target_compile_definitions(binaryninjaapi PUBLIC ${BinaryNinjaCore_DEFINITIONS}) else() + add_subdirectory(stubs EXCLUDE_FROM_ALL) + + # Be sure to only link against the stubs archive file + add_dependencies(binaryninjaapi binaryninjacore_stubs) + if(APPLE) - target_link_options(binaryninjaapi PUBLIC -undefined dynamic_lookup) + target_link_libraries(binaryninjaapi PUBLIC "$/$.dylib") elseif(MSVC) - # Generate stubs.cpp with implementations of all the BNAPI functions - execute_process(COMMAND python ${PROJECT_SOURCE_DIR}/cmake/generate_stubs.py ${PROJECT_SOURCE_DIR}/binaryninjacore.h ${PROJECT_BINARY_DIR}/stubs) - - # Compile those stubs into a stub library we can use to fool the linker - add_library(binaryninjacore SHARED ${PROJECT_BINARY_DIR}/stubs/stubs.cpp) - set_target_properties(binaryninjacore - PROPERTIES OUTPUT_NAME binaryninjacore - SOVERSION 1 - ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/stubs - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/stubs - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/stubs - ) - target_include_directories(binaryninjacore PUBLIC ${PROJECT_SOURCE_DIR}) - - # Be sure to only link against the stubs archive file - add_dependencies(binaryninjaapi binaryninjacore) if(${CMAKE_GENERATOR} MATCHES "^Visual Studio") # Visual Studio's generator adds the config to the file path - target_link_libraries(binaryninjaapi PUBLIC "$/$/$.lib") + target_link_libraries(binaryninjaapi PUBLIC "$/$/$.lib") else() - target_link_libraries(binaryninjaapi PUBLIC "$/$.lib") + target_link_libraries(binaryninjaapi PUBLIC "$/$.lib") endif() else() - target_link_options(binaryninjaapi PUBLIC "LINKER:--allow-shlib-undefined") + target_link_libraries(binaryninjaapi PUBLIC "$/$.so.1") endif() endif() diff --git a/Cargo.lock b/Cargo.lock index c66dec84..4077bebf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,7 @@ name = "binaryninjacore-sys" version = "0.1.0" dependencies = [ "bindgen", + "cmake", "proc-macro2", ] @@ -311,9 +312,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.27" +version = "1.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" dependencies = [ "jobserver", "libc", @@ -422,6 +423,15 @@ dependencies = [ "error-code", ] +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" diff --git a/cmake/generate_stubs.py b/cmake/generate_stubs.py deleted file mode 100644 index 5478ba7c..00000000 --- a/cmake/generate_stubs.py +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright (c) 2015-2025 Vector 35 Inc -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -# IN THE SOFTWARE. - -# Based on BinExport - -# Copyright 2011-2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import argparse -import os -import re -import sys - -parser = argparse.ArgumentParser(sys.argv[0]) -parser.add_argument("core_header") -parser.add_argument("build_dir") - -options = parser.parse_args() - -print(f"GENERATE STUBS: {options.core_header} -> {options.build_dir}") -print(f"{sys.executable} {' '.join(sys.argv)}") - -os.makedirs(options.build_dir, exist_ok=True) -output_source = os.path.join(options.build_dir, 'stubs.cpp') - -with open(output_source, 'w') as stubs: - stubs.write(""" -// Copyright (c) 2015-2025 Vector 35 Inc -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. - -// Based on BinExport - -// Copyright 2011-2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#define BINARYNINJACORE_LIBRARY -#include "binaryninjacore.h" -#undef BINARYNINJACORE_LIBRARY - -extern "C" { -""") - - with open(options.core_header, 'r') as header: - header_conts = header.read() - - # Quick preprocess of comments in case we have commented defs - header_conts = re.sub(r'//.*\n', '\n', header_conts) - - # Pull out all core api functions to generate stubs - for match in re.finditer( - r'(?m:)\t(BINARYNINJACOREAPI [^;]*);', - header_conts - ): - group = match.group(1) - - # Void functions don't have a return (TODO: breaks if we ever return a fn ptr) - void_group = re.search(r'(?m:)BINARYNINJACOREAPI\s+void\s+.*', group) - if void_group is not None: - stubs.write(f'{match.group(1)} {{ }}\n') - else: - stubs.write(f'{match.group(1)} {{ return {{ }}; }}\n') - - stubs.write(""" -} - """) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 0b9ac4f3..4856f97b 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -7,16 +7,20 @@ rust-version = "1.83.0" license = "Apache-2.0" [features] +default = ["core"] # This is used when statically linking to prevent exporting CorePluginABIVersion and UiPluginABIVersion. no_exports = [] # Add this if you want to support the demo version of the product. # This will disable certain functions that do not exist in the demo build. demo = ["no_exports"] +# If disabled, a stub binaryninjacore library will be generated and linked. +# This feature should only be disabled for shared libraries that will later be loaded in the same process as the real binaryninjacore. +core = ["binaryninjacore-sys/core"] [dependencies] log = { version = "0.4", features = ["std"] } rayon = { version = "1.10", optional = true } -binaryninjacore-sys = { path = "binaryninjacore-sys" } +binaryninjacore-sys = { path = "binaryninjacore-sys", default-features = false} thiserror = "2.0" serde = "1.0" serde_derive = "1.0" diff --git a/rust/binaryninjacore-sys/Cargo.toml b/rust/binaryninjacore-sys/Cargo.toml index bfd49a41..d4faaec8 100644 --- a/rust/binaryninjacore-sys/Cargo.toml +++ b/rust/binaryninjacore-sys/Cargo.toml @@ -7,8 +7,15 @@ edition = "2021" links = "binaryninjacore" license = "Apache-2.0" +[features] +default = ["core"] +# If disabled, a stub binaryninjacore library will be generated and linked. +# This feature should only be disabled for shared libraries that will later be loaded in the same process as the real binaryninjacore. +core = [] + [build-dependencies] bindgen = "0.71.1" # TODO: Remove this once bindgen correctly pins the version. # proc-macro2 v1.0.79 does not have https://docs.rs/proc-macro2/1.0.80/proc_macro2/struct.Literal.html#method.c_string -proc-macro2 = ">=1.0.80" \ No newline at end of file +proc-macro2 = ">=1.0.80" +cmake = "0.1" diff --git a/rust/binaryninjacore-sys/build.rs b/rust/binaryninjacore-sys/build.rs index 579276a6..672615da 100644 --- a/rust/binaryninjacore-sys/build.rs +++ b/rust/binaryninjacore-sys/build.rs @@ -41,6 +41,22 @@ fn link_path() -> PathBuf { }) } +// Generate and compile stub shared library and return the path to the folder containing the built stub library +fn generate_stubs() -> PathBuf { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + + let api_base_path = std::path::PathBuf::from(manifest_dir) + .join("../../") + .canonicalize() + .unwrap(); + + let stubs_path = api_base_path.join("stubs"); + cmake::Config::new(stubs_path) + .generator("Ninja") + .build() + .join("build") +} + fn main() { println!("cargo:rerun-if-env-changed=BINARYNINJADIR"); println!("cargo:rerun-if-changed=../../binaryninjacore.h"); @@ -48,9 +64,12 @@ fn main() { //Cargo's output directory let out_dir = env::var("OUT_DIR").unwrap(); - let link_path = env::var("BINARYNINJADIR") - .map(PathBuf::from) - .unwrap_or_else(|_| link_path()); + let link_path = match env::var("CARGO_FEATURE_CORE") { + Ok(_) => env::var("BINARYNINJADIR") + .map(PathBuf::from) + .unwrap_or_else(|_| link_path()), + Err(_) => generate_stubs(), + }; // Linux builds of binaryninja ship with libbinaryninjacore.so.1 in the // application folder and no symlink. The linker will attempt to link with diff --git a/rust/build.rs b/rust/build.rs index c98bf81b..1e2fede0 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; fn main() { - let link_path = - std::env::var_os("DEP_BINARYNINJACORE_PATH").expect("DEP_BINARYNINJACORE_PATH specified"); + let link_path = std::env::var_os("DEP_BINARYNINJACORE_PATH") + .expect("DEP_BINARYNINJACORE_PATH not specified"); println!("cargo::rustc-link-lib=dylib=binaryninjacore"); println!("cargo::rustc-link-search={}", link_path.to_str().unwrap()); @@ -15,7 +15,7 @@ fn main() { ); } - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR specified"); + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not specified"); let out_dir_path = PathBuf::from(out_dir); // Copy all binaries to OUT_DIR for unit tests. diff --git a/stubs/CMakeLists.txt b/stubs/CMakeLists.txt new file mode 100644 index 00000000..35ab6177 --- /dev/null +++ b/stubs/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.19 FATAL_ERROR) +project(binaryninjacore_stubs) + +if(WIN32) + set(PYTHON_COMMAND "py -3") +else() + set(PYTHON_COMMAND "python3") +endif() + +file(REAL_PATH "${PROJECT_SOURCE_DIR}/../" ABSOLUTE_API_PATH) + +# Generate stubs.cpp with implementations of all the BNAPI functions +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/stubs.cpp + COMMAND ${PYTHON_COMMAND} ${PROJECT_SOURCE_DIR}/generate_stubs.py ${ABSOLUTE_API_PATH}/binaryninjacore.h ${PROJECT_BINARY_DIR} + DEPENDS ${PROJECT_SOURCE_DIR}/generate_stubs.py ${ABSOLUTE_API_PATH}/binaryninjacore.h + COMMENT "Generating stubs.cpp from binaryninjacore.h" +) + +# Compile those stubs into a stub library we can use to fool the linker +add_library(binaryninjacore_stubs SHARED ${PROJECT_BINARY_DIR}/stubs.cpp) + +set_target_properties(binaryninjacore_stubs + PROPERTIES OUTPUT_NAME binaryninjacore + SOVERSION 1 + CXX_STANDARD 20 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ARCHIVE_OUTPUT_DIRECTORY $<1:${PROJECT_BINARY_DIR}> +) + +target_include_directories(binaryninjacore_stubs PUBLIC ${ABSOLUTE_API_PATH}) + +install(TARGETS binaryninjacore_stubs LIBRARY DESTINATION ${PROJECT_BINARY_DIR}) diff --git a/stubs/generate_stubs.py b/stubs/generate_stubs.py new file mode 100644 index 00000000..5478ba7c --- /dev/null +++ b/stubs/generate_stubs.py @@ -0,0 +1,121 @@ +# Copyright (c) 2015-2025 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +# Based on BinExport + +# Copyright 2011-2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import os +import re +import sys + +parser = argparse.ArgumentParser(sys.argv[0]) +parser.add_argument("core_header") +parser.add_argument("build_dir") + +options = parser.parse_args() + +print(f"GENERATE STUBS: {options.core_header} -> {options.build_dir}") +print(f"{sys.executable} {' '.join(sys.argv)}") + +os.makedirs(options.build_dir, exist_ok=True) +output_source = os.path.join(options.build_dir, 'stubs.cpp') + +with open(output_source, 'w') as stubs: + stubs.write(""" +// Copyright (c) 2015-2025 Vector 35 Inc +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// Based on BinExport + +// Copyright 2011-2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define BINARYNINJACORE_LIBRARY +#include "binaryninjacore.h" +#undef BINARYNINJACORE_LIBRARY + +extern "C" { +""") + + with open(options.core_header, 'r') as header: + header_conts = header.read() + + # Quick preprocess of comments in case we have commented defs + header_conts = re.sub(r'//.*\n', '\n', header_conts) + + # Pull out all core api functions to generate stubs + for match in re.finditer( + r'(?m:)\t(BINARYNINJACOREAPI [^;]*);', + header_conts + ): + group = match.group(1) + + # Void functions don't have a return (TODO: breaks if we ever return a fn ptr) + void_group = re.search(r'(?m:)BINARYNINJACOREAPI\s+void\s+.*', group) + if void_group is not None: + stubs.write(f'{match.group(1)} {{ }}\n') + else: + stubs.write(f'{match.group(1)} {{ return {{ }}; }}\n') + + stubs.write(""" +} + """) -- cgit v1.3.1