18 changed files with 309 additions and 98 deletions

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Test the public release through r2pm using radare2's official distribution.
set -euo pipefail
mkdir -p build-r2-runtime
base="https://github.com/radareorg/radare2/releases/download/${R2_REF}"
case "$RUNNER_OS" in
Linux)
curl -fsSL -o build-r2-runtime/radare2.deb "$base/radare2_${R2_REF}_amd64.deb"
sudo apt-get update -qq
sudo apt-get install -y ./build-r2-runtime/radare2.deb
;;
Windows)
curl -fsSL -o build-r2-runtime/radare2.zip "$base/radare2-${R2_REF}-w64.zip"
unzip -q build-r2-runtime/radare2.zip -d build-r2-runtime
r2=$(find "$PWD/build-r2-runtime" -name radare2.exe -type f -print -quit)
[[ -n "$r2" ]]
export PATH="$(dirname "$r2"):$PATH"
;;
*) echo "Unsupported runner: $RUNNER_OS" >&2; exit 1 ;;
esac
r2pm -U
r2pm -bi r2xsql
python3 tests/smoke.py "$(command -v radare2)"
r2pm -u r2xsql
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Exercise the source installation paths used outside the release archives.
set -euo pipefail
export PATH="$PWD/r2prefix/bin:$PATH"
export LD_LIBRARY_PATH="$PWD/r2prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export DYLD_LIBRARY_PATH="$PWD/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
export PKG_CONFIG_PATH="$PWD/r2prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"
if [[ "$RUNNER_OS" == Windows ]]; then
cmake --build build --config Release --target user-install
else
make -j2 user-install LIBXSQL_DIR="$PWD/build/_deps/libxsql-src"
fi
python3 tests/smoke.py ./r2prefix/bin/radare2
if [[ "$RUNNER_OS" == Windows ]]; then
cmake --build build --config Release --target user-uninstall
else
make user-uninstall
fi
+16 -10
View File
@@ -2,7 +2,7 @@
# Stage the same plugins/ layout on every platform for r2pm -bi.
set -euo pipefail
# Derive the target from the runner, independent of the build host's r2pm.
# Match the r2pm OS/architecture/bits naming used by the package recipe.
case "${RUNNER_OS}-${RUNNER_ARCH}" in
Linux-X64) R2PM_TRIPLET=linux-x86-64; ext=so ;;
Windows-X64) R2PM_TRIPLET=windows-x86-64; ext=dll ;;
@@ -11,24 +11,30 @@ case "${RUNNER_OS}-${RUNNER_ARCH}" in
*) echo "Unsupported runner: ${RUNNER_OS}-${RUNNER_ARCH}" >&2; exit 1 ;;
esac
export LD_LIBRARY_PATH="$PWD/r2prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
export DYLD_LIBRARY_PATH="$PWD/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
actual_r2=$(./r2prefix/bin/radare2 -NN -H R2_VERSION)
[[ "$actual_r2" == "$R2_REF" ]] || {
echo "Expected radare2 $R2_REF, found $actual_r2" >&2; exit 1;
}
rm -rf dist
mkdir -p dist/plugin/plugins
mkdir -p dist/plugin/plugins dist/plugin/licenses
cp "build/bin/core_r2xsql.${ext}" dist/plugin/plugins/
cp LICENSE dist/plugin/
libxsql_dir=${LIBXSQL_DIR:-build/_deps/libxsql-src}
cp "$libxsql_dir/LICENSE" dist/plugin/licenses/libxsql.txt
cp "$libxsql_dir/external/nlohmann/LICENSE" dist/plugin/licenses/nlohmann-json.txt
# Check automatic discovery of the packaged plugin, including its suffix.
result=$(R2_LIBR_PLUGINS="$PWD/dist/plugin/plugins" \
LD_LIBRARY_PATH="$PWD/r2prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
DYLD_LIBRARY_PATH="$PWD/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" \
./r2prefix/bin/radare2 -Nq -c 'sqlj SELECT 1 AS ok' -)
[[ "$result" == *'"rows":[["1"]]'* ]] || { echo "$result" >&2; exit 1; }
# Test the packaged binary, automatic discovery and both process exit paths.
R2_LIBR_PLUGINS="$PWD/dist/plugin/plugins" \
python3 tests/smoke.py ./r2prefix/bin/radare2
cat > dist/plugin/README.txt <<EOF
r2xsql — radare2 core plugin
Built against radare2 ${R2_REF} for ${R2PM_TRIPLET}.
Install with a matching radare2 version and an r2pm supporting
R2PM_VERSION_MAP (available in radare2 git; planned for 6.2.6):
Install with a matching radare2 version and a recent r2pm:
r2pm -U
r2pm -bi r2xsql
+36 -2
View File
@@ -18,7 +18,7 @@ concurrency:
# Build against this radare2 release without changing its version for r2pm.
env:
R2_REF: "6.2.4"
R2_REF: "6.2.6"
jobs:
# ============================================================================
@@ -26,7 +26,8 @@ jobs:
# ============================================================================
build-linux:
name: Build (Linux)
runs-on: ubuntu-latest
# Keep the release's glibc/libstdc++ baseline stable as ubuntu-latest moves.
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -63,6 +64,10 @@ jobs:
export LD_LIBRARY_PATH="$GITHUB_WORKSPACE/r2prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
make -C tests R2XSQL_PLUGIN="$GITHUB_WORKSPACE/build/bin/core_r2xsql.so"
- name: Check source user installation
shell: bash
run: bash .github/scripts/check_user_install.sh
- name: Stage artifacts
id: stage
shell: bash
@@ -127,6 +132,10 @@ jobs:
-DRadare2_ROOT="$GITHUB_WORKSPACE/r2prefix"
cmake --build build --config Release
- name: Check source user installation
shell: bash
run: bash .github/scripts/check_user_install.sh
- name: Stage artifacts
id: stage
shell: bash
@@ -171,6 +180,16 @@ jobs:
-DRadare2_ROOT="$GITHUB_WORKSPACE/r2prefix"
cmake --build build --config Release
- name: Run r2r regression tests
run: |
export PATH="$GITHUB_WORKSPACE/r2prefix/bin:$PATH"
export DYLD_LIBRARY_PATH="$GITHUB_WORKSPACE/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
make -C tests R2XSQL_PLUGIN="$GITHUB_WORKSPACE/build/bin/core_r2xsql.dylib"
- name: Check source user installation
shell: bash
run: bash .github/scripts/check_user_install.sh
- name: Stage artifacts
id: stage
shell: bash
@@ -214,3 +233,18 @@ jobs:
files: |
r2xsql-*.zip
generate_release_notes: true
verify-release:
name: Install release (${{ matrix.os }})
if: startsWith(github.ref, 'refs/tags/')
needs: publish-release
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install and test the published r2pm binary
shell: bash
run: bash .github/scripts/check_binary_install.sh
+6 -2
View File
@@ -43,8 +43,11 @@ endif
# $(1) is the included makefile stamp, refreshed after a managed checkout.
define check_libxsql
@mkdir -p $(dir $(1))
@current=$$(git -C "$(LIBXSQL_DIR)" rev-parse --verify HEAD 2>/dev/null || echo unknown); \
expected=$$(git -C "$(LIBXSQL_DIR)" rev-parse --verify "$(LIBXSQL_REF)^{commit}" 2>/dev/null); \
@current=unknown; expected=unknown; \
if [ -e "$(LIBXSQL_DIR)/.git" ]; then \
current=$$(git -C "$(LIBXSQL_DIR)" rev-parse --verify HEAD 2>/dev/null || echo unknown); \
expected=$$(git -C "$(LIBXSQL_DIR)" rev-parse --verify "$(LIBXSQL_REF)^{commit}" 2>/dev/null); \
fi; \
if [ "$$current" != "$$expected" ]; then \
if [ "$(LIBXSQL_DIR)" = "$(BUILD)/libxsql" ]; then \
git -C "$(LIBXSQL_DIR)" fetch origin tag "$(LIBXSQL_REF)" && \
@@ -141,6 +144,7 @@ ifneq (,$(wildcard /usr/bin/codesign))
endif
install: $(PLUGIN)
@test -n "$(PLUGDIR)" || { echo "Cannot determine radare2 plugin directory; set PLUGDIR." >&2; exit 1; }
mkdir -p "$(DESTDIR)$(PLUGDIR)"
rm -f "$(DESTDIR)$(PLUGDIR)/core_r2xsql.$(R2_LIBEXT)"
cp -f $(PLUGIN) "$(DESTDIR)$(PLUGDIR)/core_r2xsql.$(R2_LIBEXT)"
+6 -2
View File
@@ -8,14 +8,18 @@ the host's live analysis as SQL tables, including writable annotations.
## Using the plugin
Install radare2 first, then build and install the matching plugin with r2pm:
Install radare2 6.2.6, then install the matching release plugin with r2pm:
```sh
r2pm -U
r2pm -ci r2xsql
r2pm -bi r2xsql
r2 -A ./target
```
To build from source on Linux/macOS, use `r2pm -ci r2xsql` or run
`make user-install` in a checkout. Windows source builds use CMake's
`user-install` target. See [build instructions](docs/development.md).
Installed plugins load automatically. Check `Lcj` and `sql?` at the r2 prompt.
For an uninstalled build, use `r2 -i ./core_r2xsql.so -A ./target`; inside an
existing session use `L ./core_r2xsql.so`. Substitute `.dylib` or `.dll` as
+22 -11
View File
@@ -22,7 +22,7 @@
# Radare2::libr (INTERFACE bundle of the lot)
#
# Plus:
# Radare2_EXECUTABLE — path to radare2(.exe) (for the r2pipe backend)
# Radare2_EXECUTABLE — path to radare2(.exe)
set(_r2_search_roots "")
@@ -66,18 +66,28 @@ endif()
if(NOT Radare2_FOUND)
find_package(PkgConfig QUIET)
if(PkgConfig_FOUND)
pkg_check_modules(_R2 QUIET r_core)
pkg_check_modules(_R2 QUIET IMPORTED_TARGET r_core)
if(_R2_FOUND)
set(Radare2_FOUND TRUE)
pkg_get_variable(Radare2_ROOT r_core prefix)
set(Radare2_VERSION "${_R2_VERSION}")
set(Radare2_INCLUDE_DIRS "${_R2_INCLUDE_DIRS}")
set(Radare2_LIBRARY_DIRS "${_R2_LIBRARY_DIRS}")
set(Radare2_LIBRARIES "${_R2_LIBRARIES}")
set(Radare2_LIBRARIES PkgConfig::_R2)
# r_core.pc includes its public dependencies, including r_util. Keep
# pkg-config's link flags and paths instead of guessing a libdir.
foreach(_target libr r_util)
if(NOT TARGET Radare2::${_target})
add_library(Radare2::${_target} INTERFACE IMPORTED)
set_target_properties(Radare2::${_target} PROPERTIES
INTERFACE_LINK_LIBRARIES PkgConfig::_R2)
endif()
endforeach()
endif()
endif()
endif()
if(Radare2_FOUND AND Radare2_ROOT)
if(_r2_resolved_root)
# Normalize separators: on Windows, Git-Bash passes -DRadare2_ROOT as
# "D:\a\...\r2prefix" (backslashes), and file(GLOB) treats backslashes as escapes
# rather than separators, so the libdir glob below matches nothing and we end up
@@ -118,8 +128,8 @@ if(Radare2_FOUND AND Radare2_ROOT)
endif()
set(_r2_libs r_core r_anal r_bin r_util r_io r_cons r_main r_socket
r_reg r_arch r_search r_syscall r_config r_lang r_egg r_debug
r_bp r_crypto r_flag r_fs r_magic r_parse)
r_reg r_arch r_asm r_esil r_search r_syscall r_config r_lang r_egg r_debug
r_bp r_crypto r_muta r_flag r_fs r_magic r_parse)
set(Radare2_LIBRARIES "")
foreach(_lib IN LISTS _r2_libs)
set(_full "")
@@ -148,10 +158,8 @@ if(Radare2_FOUND AND Radare2_ROOT)
# r_core_* symbols. Surface it at configure time instead.
list(LENGTH Radare2_LIBRARIES _r2_nlibs)
message(STATUS "FindRadare2: libdir='${_r2_libdir}', resolved ${_r2_nlibs} libraries")
if(_r2_nlibs EQUAL 0)
message(WARNING "FindRadare2: found radare2 at '${Radare2_ROOT}' but located ZERO "
"import libraries under '${_r2_libdir}'. Full/plugin links will fail "
"with undefined r_core_* symbols.")
if(NOT TARGET Radare2::r_core OR NOT TARGET Radare2::r_util)
set(Radare2_LIBRARIES "")
endif()
if(NOT TARGET Radare2::libr)
@@ -161,6 +169,9 @@ if(Radare2_FOUND AND Radare2_ROOT)
INTERFACE_LINK_LIBRARIES "${Radare2_LIBRARIES}")
endif()
endif()
if(Radare2_FOUND)
find_program(Radare2_EXECUTABLE
NAMES radare2 r2
PATHS "${Radare2_ROOT}/bin"
@@ -172,7 +183,7 @@ endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Radare2
REQUIRED_VARS Radare2_ROOT Radare2_INCLUDE_DIRS
REQUIRED_VARS Radare2_INCLUDE_DIRS Radare2_LIBRARIES
VERSION_VAR Radare2_VERSION)
mark_as_advanced(Radare2_INCLUDE_DIRS Radare2_LIBRARIES Radare2_EXECUTABLE)
+20
View File
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: LicenseRef-Human-Origin-Source-1.0
# Query at install time so cross builds can still configure without running r2.
if(NOT PLUGIN_DIR)
execute_process(COMMAND "${RADARE2}" -NN -H R2_USER_PLUGINS
RESULT_VARIABLE _status OUTPUT_VARIABLE PLUGIN_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE)
if(NOT _status STREQUAL "0" OR NOT PLUGIN_DIR)
message(FATAL_ERROR "Cannot determine radare2 user plugin directory; set R2XSQL_USER_PLUGINS.")
endif()
endif()
file(TO_CMAKE_PATH "${PLUGIN_DIR}" PLUGIN_DIR)
get_filename_component(_name "${PLUGIN}" NAME)
if(ACTION STREQUAL "user-install")
file(INSTALL "${PLUGIN}" DESTINATION "${PLUGIN_DIR}" TYPE FILE)
elseif(ACTION STREQUAL "user-uninstall")
file(REMOVE "${PLUGIN_DIR}/${_name}")
else()
message(FATAL_ERROR "Unknown install action: ${ACTION}")
endif()
message(STATUS "${ACTION}: ${PLUGIN_DIR}/${_name}")
+29 -27
View File
@@ -16,6 +16,7 @@ Windows (MSVC):
cmake -S . -B build -G "Visual Studio 17 2022" -A x64 `
-DRadare2_ROOT=C:\path\to\radare2\prefix
cmake --build build --config Release
cmake --build build --config Release --target user-install
```
`Radare2_ROOT` is a radare2 **install prefix** containing `include/libr/`
@@ -23,6 +24,12 @@ and `lib/`. CMake requires radare2 and builds the libr backend and plugin by
default; a missing radare2 installation is a configure error. The plugin is in
`build/bin/` (`build/bin/Release/` for the MSVC Release configuration).
CMake's `user-install` target installs the plugin in the directory reported by
the matching radare2's `-NN -H R2_USER_PLUGINS`, without administrator access.
`user-uninstall` removes it. Override the destination at configure time with
`-DR2XSQL_USER_PLUGINS=/path/to/plugins` if needed. Both targets also work on
Linux/macOS; omit `--config Release` with a single-configuration generator.
`R2XSQL_BUILD_PLUGIN=OFF` disables the plugin target and
`R2XSQL_BUILD_EXAMPLES=OFF` disables examples. C++ consumers can embed the
library with `open_libr` and the `r2xsql_libr` target. The project also supports
@@ -42,7 +49,7 @@ make user-install # install into the per-user plugin dir (no root)
make install # install system-wide (R2_LIBR_PLUGINS)
make user-uninstall # remove the per-user plugin without building
make uninstall # remove the system-wide plugin without building
make test # load it in r2 and run one SQL query
make test # run the command and HTTP regression suite
# only `radare2` (not `r2`) on PATH? -> make R2=radare2
```
@@ -64,34 +71,29 @@ consume [`libxsql`](https://github.com/0xeb/libxsql): the Makefile uses a
sibling `../libxsql` checkout (else clones it), and meson pulls it as a
subproject. All three builds pin the same libxsql release tag (`v1.0.21`).
## Binary releases
When published, binary release assets are named `r2xsql-<radare2-version>-<R2PM_TRIPLET>.zip`, for
example `r2xsql-6.2.6-linux-x86-64.zip`. The first release, `v0.0.1`, targets
radare2 **6.2.6** on Linux x86-64, Windows x86-64 and macOS ARM64. The r2pm
package uses `R2PM_VERSION_MAP "6.2.6:v0.0.1"` to select this release.
Each archive contains the plugin in `plugins/` and dependency license notices.
With r2pm 6.2.6 or newer, use `r2pm -U` followed by
`r2pm -bi r2xsql`; uninstall with `r2pm -u r2xsql`. Only matching radare2
versions and targets are installed. To install manually, copy the plugin to
the directory printed by `radare2 -NN -H R2_USER_PLUGINS`.
The plugin release version and radare2 version are separate. A plugin release
such as `v0.0.1` contains assets named
`r2xsql-<radare2-version>-<R2PM_TRIPLET>.zip`, for example
`r2xsql-6.2.4-linux-x86-64.zip`. Each archive contains the plugin in `plugins/`.
The current CI targets Linux x64, Windows x64 and macOS ARM64 against radare2
6.2.4. A different engine ABI needs a matching build, including iaito's engine.
Windows binaries use the dynamic MSVC runtime, matching radare2's MSVC build.
Use the matching radare2 distribution and its runtime dependencies; the plugin
archive does not bundle another copy of radare2's DLLs.
Pushing a `v*` tag runs the build workflow and, after all platform builds
succeed, creates a GitHub Release with the ZIPs attached. Branch builds and
manual workflow runs upload CI artifacts without publishing a release.
Keep `R2_REF` in `.github/workflows/build.yml` and the compatibility table in
[`radare2-pm/db/r2xsql`](https://github.com/radareorg/radare2-pm/blob/master/db/r2xsql)
aligned. The recipe uses `R2PM_VERSION_MAP` to map the installed radare2 version
(`R2V`) to the plugin release tag (`R2PM_VERSION`). It downloads from that exact
release, retaining `R2V` in the asset name to select the compatible build.
Add a mapping only when that release supplies a build for the engine version;
a new r2pm version alone does not make an older plugin ABI compatible.
Binary installation requires r2pm with `R2PM_VERSION_MAP` support, available in
radare2 git and planned for radare2 6.2.6. Radare2 6.2.4 introduced binary
installation, but its r2pm lacks this mapping facility. The mapped release
and matching target asset must also be published; a CI artifact alone is not
enough. With a suitable r2pm, use `r2pm -U` then `r2pm -bi r2xsql`; uninstall
with `r2pm -u r2xsql`. Unsupported versions or targets fail instead of falling
back to a source build. To install manually, copy the matching plugin to the
directory printed by `radare2 -NN -H R2_USER_PLUGINS`.
Tag the release as `v0.0.1` after the build workflow passes. Tag builds publish
the three ZIPs only after every platform succeeds. CI tests automatic plugin
discovery, JSON/CSV output, comment writes, error recovery, and normal/quick
process exit on all three platforms, plus the r2r suite on Linux and macOS.
CI also builds and checks `make user-install` on Linux/macOS and CMake's
`user-install` target on Windows before publishing.
After publication, CI installs the public binaries with `r2pm -bi r2xsql`
against the official Linux and Windows radare2 distributions and runs the
same smoke checks.
## Tests
+7 -3
View File
@@ -10,16 +10,20 @@ Install radare2, then install a matching r2xsql plugin:
```sh
r2pm -U
r2pm -ci r2xsql
r2pm -bi r2xsql
```
Release `v0.0.1` targets radare2 6.2.6. For a source build on Linux/macOS,
use `r2pm -ci r2xsql` or `make user-install`. Windows source builds use
`cmake --build build --config Release --target user-install` after
[configuring CMake](development.md).
Installed plugins load automatically on normal startup. `make user-install`
does the same for a local build. Manual installation uses the directory
reported by `r2 -NN -H R2_USER_PLUGINS`. Restart a host after installation.
The plugin's radare2 ABI must match the engine loading it; iaito can use a
different engine from the standalone r2 on PATH. Binary installation with
`r2pm -bi r2xsql` requires an available asset matching the host and platform,
plus r2pm with `R2PM_VERSION_MAP` support (radare2 git; planned for 6.2.6).
`r2pm -bi r2xsql` requires an available asset matching the host and platform.
See [building and packaging](development.md).
For a build that is not installed:
+19 -3
View File
@@ -38,6 +38,18 @@ void for_each_rlist(RList* list, F&& fn) {
}
}
template <typename F>
void for_each_function_block(RAnalFunction* fcn, F&& fn) {
#if R2_ABIVERSION >= 168
RAnalBlock** it;
R_VEC_FOREACH(&fcn->bbs, it) {
if (*it) fn(*it);
}
#else
for_each_rlist<RAnalBlock>(fcn->bbs, fn);
#endif
}
// `afxj` and `axfj` both print `r_anal_ref_type_tostring(ref->type)`
// unconditionally, so a zero-typed ref reads back as the literal "NULL".
std::string ref_type_string(RAnalRefType type) {
@@ -818,7 +830,11 @@ class LibrApi : public Api {
free(name);
}
r.size = static_cast<int64_t>(r_anal_function_linear_size(fcn));
#if R2_ABIVERSION >= 168
r.nbbs = static_cast<int64_t>(RVecAnalBlockPtr_length(&fcn->bbs));
#else
r.nbbs = static_cast<int64_t>(r_list_length(fcn->bbs));
#endif
int ebbs = 0;
r.edges = r_anal_function_count_edges(fcn, &ebbs);
r.cc = r_anal_function_complexity(fcn);
@@ -898,7 +914,7 @@ class LibrApi : public Api {
// tables use is always keyed on a function START, so mirror that here too
// — an address merely inside a function must not silently answer for it.
if (!fcn || static_cast<int64_t>(fcn->addr) != func_addr) return;
for_each_rlist<RAnalBlock>(fcn->bbs, [&](RAnalBlock* b) {
for_each_function_block(fcn, [&](RAnalBlock* b) {
BlockData d;
fill_block(b, d);
out.push_back(std::move(d));
@@ -914,7 +930,7 @@ class LibrApi : public Api {
if (!core_ || !core_->anal) return;
for_each_rlist<RAnalFunction>(core_->anal->fcns, [&](RAnalFunction* fcn) {
std::vector<BlockData> blocks;
for_each_rlist<RAnalBlock>(fcn->bbs, [&](RAnalBlock* b) {
for_each_function_block(fcn, [&](RAnalBlock* b) {
BlockData d;
fill_block(b, d);
blocks.push_back(std::move(d));
@@ -1063,7 +1079,7 @@ class LibrApi : public Api {
void instructions_for_func(RAnalFunction* fcn, std::vector<InsnRow>& out) {
struct Bb { ut64 addr; int size; };
std::vector<Bb> bbs;
for_each_rlist<RAnalBlock>(fcn->bbs, [&](RAnalBlock* b) {
for_each_function_block(fcn, [&](RAnalBlock* b) {
if (b->size > 0) bbs.push_back({b->addr, static_cast<int>(b->size)});
});
std::sort(bbs.begin(), bbs.end(),
+4 -1
View File
@@ -140,7 +140,10 @@ void build_data_items(Backend* be, std::vector<DataItemRow>& rows) {
r.has_value = !r.value_repr.empty();
r.segment_name = j.section;
r.has_segment = !r.segment_name.empty();
if (!r.has_size) { r.size = j.size; r.has_size = true; }
// Mach-O symbols and ELF mapping symbols (e.g. ARM's $d) can have
// unknown/zero size. Preserve a known symbol size, otherwise use the
// string's byte size, including its terminator.
if (!r.has_size || r.size <= 0) { r.size = j.size; r.has_size = true; }
}
rows.reserve(by_addr.size());
+15
View File
@@ -51,3 +51,18 @@ xsql_add_versioninfo(core_r2xsql
FILETYPE VFT_DLL
DESCRIPTION "r2xsql in-r2 plugin (sql command)"
ORIGINAL_FILENAME "core_r2xsql.dll")
if(R2XSQL_STANDALONE)
set(R2XSQL_USER_PLUGINS "" CACHE PATH "Override the per-user radare2 plugin directory")
foreach(_action user-install user-uninstall)
add_custom_target(${_action}
COMMAND ${CMAKE_COMMAND}
"-DRADARE2=${Radare2_EXECUTABLE}"
"-DPLUGIN=$<TARGET_FILE:core_r2xsql>"
"-DPLUGIN_DIR=${R2XSQL_USER_PLUGINS}"
"-DACTION=${_action}"
-P "${PROJECT_SOURCE_DIR}/cmake/user-install.cmake"
VERBATIM)
endforeach()
add_dependencies(user-install core_r2xsql)
endif()
+37 -33
View File
@@ -17,8 +17,8 @@
// sql.views list available views
//
// The plugin wraps the host RCore* with libr2xsql's LibrBackend (no
// ownership) and shares a single Session across calls so SQLite caches
// stay warm across queries.
// ownership) and keeps one Session per core across calls so SQLite caches
// stay warm across queries. radare2 owns its lifetime through ctx->data.
#include <r2xsql/r2xsql.hpp>
#include <xsql/query_script.hpp>
@@ -30,6 +30,7 @@
#include <cstring>
#include <memory>
#include <mutex>
#include <new>
#include <string>
// r2xsql::make_libr_backend_from_core (declared in <r2xsql/backend.hpp>, included
@@ -39,18 +40,16 @@ namespace {
struct PluginState {
std::unique_ptr<r2xsql::Session> session;
RCore* bound_core = nullptr;
std::mutex mu;
};
PluginState g_state;
void ensure_session(RCore* core) {
std::lock_guard<std::mutex> lock(g_state.mu);
if (g_state.session && g_state.bound_core == core) return;
// Called with state.mu held. Do not keep a namespace-scope owning Session:
// quick exit skips plugin_fini, and static destruction can close SQLite after
// libxsql's cache registry has already been destroyed.
void ensure_session(PluginState& state, RCore* core) {
if (state.session) return;
auto backend = r2xsql::make_libr_backend_from_core(core);
g_state.session = std::make_unique<r2xsql::Session>(std::move(backend));
g_state.bound_core = core;
state.session = std::make_unique<r2xsql::Session>(std::move(backend));
}
static RCoreHelpMessage help_msg_sql = {
@@ -106,7 +105,7 @@ std::string render_table(RCore* core, const xsql::Result& res,
return out ? out.get() : "";
}
bool dispatch(RCore* core, const char* input) {
bool dispatch(PluginState& state, RCore* core, const char* input) {
if (!input) return false;
std::string table_query;
@@ -151,15 +150,12 @@ bool dispatch(RCore* core, const char* input) {
return true;
}
ensure_session(core);
std::string out;
{
// Serialize against concurrent callers (e.g. from other plugins or tools)
// which also touch g_state.session / SQLite under this same mutex.
std::lock_guard<std::mutex> lock(g_state.mu);
std::lock_guard<std::mutex> lock(state.mu);
try {
auto res = g_state.session->query(input);
ensure_session(state, core);
auto res = state.session->query(input);
out = render_table(core, res, table_query.c_str());
} catch (const std::exception& e) {
out = std::string("EXCEPTION: ") + e.what() + "\n";
@@ -171,7 +167,7 @@ bool dispatch(RCore* core, const char* input) {
return true;
}
bool dispatch_json(RCore* core, const char* input) {
bool dispatch_json(PluginState& state, RCore* core, const char* input) {
input = r_str_trim_head_ro(input);
if (!*input || std::strcmp(input, "?") == 0) {
print_help(core);
@@ -179,10 +175,10 @@ bool dispatch_json(RCore* core, const char* input) {
}
std::string out;
try {
ensure_session(core);
std::lock_guard<std::mutex> lock(g_state.mu);
auto exec = [](const std::string& sql, xsql::ScriptStatementResult& result) {
auto res = g_state.session->query(sql);
std::lock_guard<std::mutex> lock(state.mu);
ensure_session(state, core);
auto exec = [&state](const std::string& sql, xsql::ScriptStatementResult& result) {
auto res = state.session->query(sql);
result.success = res.ok();
result.error = res.error;
result.warnings = std::move(res.warnings);
@@ -205,11 +201,12 @@ bool dispatch_json(RCore* core, const char* input) {
}
bool plugin_call(RCorePluginSession* ctx, const char* input) {
if (!ctx || !input) return false;
if (!ctx || !ctx->data || !input) return false;
auto& state = *static_cast<PluginState*>(ctx->data);
if (std::strncmp(input, "sqlj", 4) == 0) {
const char* sql = input + 4;
if (*sql && *sql != ' ' && *sql != '\t' && *sql != '?') return false;
return dispatch_json(ctx->core, sql);
return dispatch_json(state, ctx->core, sql);
}
// r2 calls every registered plugin's call() for every command; only
// claim the ones that start with our prefix.
@@ -219,17 +216,24 @@ bool plugin_call(RCorePluginSession* ctx, const char* input) {
if (*rest != '\0' && *rest != ' ' && *rest != '?' && *rest != '.' && *rest != ',') {
return false;
}
return dispatch(ctx->core, rest);
return dispatch(state, ctx->core, rest);
}
bool plugin_init(RCorePluginSession* /*ctx*/) {
return true;
bool plugin_init(RCorePluginSession* ctx) {
ctx->data = new (std::nothrow) PluginState;
return ctx->data != nullptr;
}
bool plugin_fini(RCorePluginSession* /*ctx*/) {
std::lock_guard<std::mutex> lock(g_state.mu);
g_state.session.reset();
g_state.bound_core = nullptr;
bool plugin_fini(RCorePluginSession* ctx) {
auto* state = static_cast<PluginState*>(ctx->data);
if (state) {
{
std::lock_guard<std::mutex> lock(state->mu);
state->session.reset();
}
delete state;
ctx->data = nullptr;
}
return true;
}
@@ -242,10 +246,10 @@ static RCorePlugin r_core_plugin_r2xsql = {
/*.name =*/ const_cast<char*>("r2xsql"),
/*.desc =*/ const_cast<char*>("SQL interface for radare2 (libr2xsql)"),
/*.author =*/ const_cast<char*>("0xeb"),
/*.version =*/ const_cast<char*>("0.0.1"),
/*.version =*/ const_cast<char*>(R2XSQL_VERSION_STRING),
/*.license =*/ const_cast<char*>("LicenseRef-Human-Origin-Source-1.0"),
/*.contact =*/ const_cast<char*>("[email protected]"),
/*.copyright =*/ const_cast<char*>("Copyright (c) 2024-2026 Elias Bachaalany"),
/*.copyright =*/ const_cast<char*>(R2XSQL_COPYRIGHT_STRING),
/*.status =*/ R_PLUGIN_STATUS_BASIC,
},
/*.init =*/ plugin_init,
+2 -2
View File
@@ -1,9 +1,9 @@
NAME=native IO maps keep metacharacters in names as data
NAME=native IO maps sanitize names without executing metacharacters
FILE=malloc://64
ARGS=-e log.level=0 -i build/load.r2
CMDS=<<CMDS
'sql,:quiet INSERT INTO io_maps(start_addr,end_addr,paddr,perm,name) VALUES(4096,4112,4,'r-x','map ; f map_injected $(f map_subst) ''quote'' # tail' || char(10) || 'f map_newline')
'sql,:quiet SELECT count(*) FROM io_maps WHERE start_addr=4096 AND end_addr=4112 AND paddr=4 AND perm='r-x' AND name='map ; f map_injected $(f map_subst) ''quote'' # tail' || char(10) || 'f map_newline'
'sql,:quiet SELECT count(*) FROM io_maps WHERE start_addr=4096 AND end_addr=4112 AND paddr=4 AND perm='r-x' AND name LIKE 'map%map_injected%map_subst%quote%tail%map_newline'
'sql,:quiet SELECT count(*) FROM flags WHERE name IN ('map_injected','map_subst','map_newline')
'sql,:quiet DELETE FROM io_maps WHERE start_addr=4096
'sql,:quiet SELECT count(*) FROM io_maps WHERE start_addr=4096
+1 -1
View File
@@ -3,7 +3,7 @@ FILE=build/fixture.bin
ARGS=-e log.level=0 -i build/load.r2
CMDS=<<CMDS
aaa
"sql,:quiet SELECT count(*)=1 FROM data_items WHERE name='r2xsql_marker' AND size>0 AND is_string=0 AND is_initialized=1"
"sql,:quiet SELECT count(*)=1 FROM data_items WHERE name IN ('r2xsql_marker','_r2xsql_marker') AND is_string=0 AND is_initialized=1"
"sql,:quiet SELECT count(*)>0 FROM data_items WHERE is_string=1 AND value_repr='r2xsql_test_marker_string' AND size>length(value_repr)"
"sql,:quiet SELECT (SELECT count(*) FROM grep WHERE kind='func')=(SELECT count(*) FROM funcs)"
"sql,:quiet SELECT (SELECT count(*) FROM grep WHERE kind='import')=(SELECT count(*) FROM imports)"
+1 -1
View File
@@ -31,7 +31,7 @@ def test_r2_http(r2_bin, r2xsql_plugin, fixture_binary, tmp_path, post):
with log_path.open("w") as log:
proc = subprocess.Popen([
r2_bin, "-NN", "-q", "-e", "http.bind=127.0.0.1", "-e", f"http.port={port}",
"-e", "http.log=false", "-e", "scr.color=0",
"-e", "http.log=false", "-e", "http.sandbox=false", "-e", "scr.color=0",
"-c", f"L {r2xsql_plugin}", "-c", "aa", "-c", "=h", str(fixture_binary),
], stdout=log, stderr=log, cwd=tmp_path)
try:
+45
View File
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: LicenseRef-Human-Origin-Source-1.0
"""Check an automatically discovered release plugin on every supported OS."""
import json
from pathlib import Path
import subprocess
import sys
def check(r2):
# -Q skips plugin finalizers; process teardown must still be safe.
for quiet in ("-q", "-Q"):
commands = [
'"sqlj SELECT NULL AS missing, 7 > 3 AS ok; SELECT 2 AS n"',
'"sqlj INSERT INTO comments(addr,text) VALUES(4, \'release smoke\'); '
"SELECT text FROM comments WHERE addr=4; "
"DELETE FROM comments WHERE addr=4; "
'SELECT count(*) FROM comments WHERE addr=4"',
'"sqlj SELECT * FROM missing_table"',
'"sqlj SELECT 42 AS alive"',
'"sql,:csv SELECT 1 AS n, \'a,b\' AS name"',
]
args = [r2, "-N", quiet, "-e", "scr.color=0", "-e", "scr.utf8=false"]
for command in commands:
args.extend(["-c", command])
result = subprocess.run(args + ["malloc://64"], capture_output=True,
text=True, timeout=30)
assert result.returncode == 0, (quiet, result.returncode, result.stderr)
lines = result.stdout.splitlines()
assert len(lines) == 6, (result.stdout, result.stderr)
multi, write, error, alive = map(json.loads, lines[:4])
assert multi["success"] and multi["statement_count"] == 2, multi
assert multi["results"][0]["rows"] == [[None, "1"]], multi
assert multi["results"][1]["rows"] == [["2"]], multi
assert write["success"] and write["statement_count"] == 4, write
assert write["results"][1]["rows"] == [["release smoke"]], write
assert write["results"][3]["rows"] == [["0"]], write
assert not error["success"], error
assert alive["success"] and alive["results"][0]["rows"] == [["42"]], alive
assert lines[4:] == ["n,name", '1,"a,b"'], result.stdout
print("Packaged plugin: JSON, CRUD, errors, CSV and process cleanup OK")
if __name__ == "__main__":
check(str(Path(sys.argv[1]).resolve()))