18 changed files with 326 additions and 73 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 -9
View File
@@ -2,7 +2,7 @@
# Stage the same plugins/ layout on every platform for r2pm -bi.
set -euo pipefail
# The pinned radare2 predates r2pm -H R2PM_TRIPLET.
# 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,17 +11,24 @@ 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
@@ -48,7 +55,7 @@ with double quotes to preserve SQL operators and statement separators.
Analysis is explicit: run aaa or start radare2 with -A.
The plugin also works through radare2's own HTTP server:
radare2 -e http.bind=127.0.0.1 -e http.port=9090 -c aaa -c '=h' <file>
radare2 -e http.bind=127.0.0.1 -e http.sandbox=false -e http.port=9090 -c aaa -c '=h' <file>
curl --data-binary '"sqlj SELECT name FROM funcs LIMIT 5"' http://127.0.0.1:9090/cmd/
Read the command output for errors even when HTTP returns status 200.
+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.2"
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
+26 -4
View File
@@ -40,6 +40,26 @@ ifeq ($(wildcard $(LIBXSQL_DIR)/CMakeLists.txt),)
LIBXSQL_DIR:=$(BUILD)/libxsql
endif
# $(1) is the included makefile stamp, refreshed after a managed checkout.
define check_libxsql
@mkdir -p $(dir $(1))
@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)" && \
git -C "$(LIBXSQL_DIR)" checkout "$(LIBXSQL_REF)" && touch $(1) || exit $$?; \
elif [ "$$current" != unknown ]; then \
echo "ERROR: libxsql at $$current, expected $(LIBXSQL_REF). Update $(LIBXSQL_DIR) manually." >&2; \
exit 1; \
fi; \
fi
@test -f $(1) || touch $(1)
endef
# --- radare2 (in-process libr backend) ----------------------------------------
R2_CFLAGS=$(shell pkg-config --cflags r_core)
R2_LDFLAGS=$(shell pkg-config --libs r_core)
@@ -84,7 +104,7 @@ all: $(PLUGIN)
# Removing files must not fetch or rebuild dependencies via included makefiles.
ifneq ($(filter-out clean mrproper uninstall user-uninstall,$(or $(MAKECMDGOALS),all)),)
-include $(BUILD)/libxsql.mk
include $(BUILD)/libxsql.mk
-include $(OBJS:.o=.d)
endif
@@ -95,9 +115,10 @@ $(LIBXSQL_DIR)/CMakeLists.txt:
@echo "libxsql not found at ../libxsql -- cloning $(LIBXSQL_URL)"
git clone --depth 1 --branch $(LIBXSQL_REF) $(LIBXSQL_URL) $(LIBXSQL_DIR)
$(BUILD)/libxsql.mk: $(LIBXSQL_DIR)/CMakeLists.txt
@mkdir -p $(dir $@)
@touch $@
# Check before reading sources; a checkout restarts make with the new source list.
.PHONY: libxsql-check
$(BUILD)/libxsql.mk: $(LIBXSQL_DIR)/CMakeLists.txt libxsql-check
$(call check_libxsql,$@)
# Rebuild legacy objects without .d files when this Makefile is updated.
$(BUILD)/obj/sqlite/sqlite3.o: $(LIBXSQL_DIR)/external/sqlite/sqlite3.c Makefile | $(LIBXSQL_DIR)/CMakeLists.txt
@@ -123,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}")
+27 -3
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
```
@@ -65,12 +72,29 @@ 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`).
When published, binary release assets are named `r2xsql-<radare2-version>-<R2PM_TRIPLET>.zip`, for
example `r2xsql-6.2.4-linux-x86-64.zip`. Each contains the plugin in `plugins/`.
With a recent r2pm supporting binary installs, use `r2pm -U` followed by
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`.
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.
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
Run the regression suite with radare2, `r2r`, a C compiler and Python 3 on
+6 -1
View File
@@ -10,9 +10,14 @@ 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.
+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()))