-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started: Integration Demo w SQLite
This page is a practical, hands-on implementation of several integration steps outlined in the Getting Started: Integrating Sigminer wiki page. We will clone the SQLite codebase and integrate Sigminer into it. To demonstrate a successful integration, we will add a simple BPF trace-script generator as a tool within SQLite's codebase.
To start, we have cloned SQLite and also cloned Sigminer internally. The following commands were run in sequence to get set up here:
git clone https://github.com/sqlite/sqlite.git
cd sqlite
git clone https://github.com/qasimkamran/Sigminer.git external/Sigminer
Now that we have our files where we want them, let us first create an empty C file in the tool/ directory, where the repository intends for us to put these supplementary utility files. We have created tool/bt_gen.c and left it empty.
Before we started developing, we needed to integrate Sigminer into the build system for this project and also include our file in the build. For this project, main.mk is the main Makefile we should use after running ./configure in the project root.
I first copied over the same template that the reference wiki provided me to the bottom of this file and then modified it to achieve this:
CXX ?= c++
SIGMINER_DIR ?= $(TOP)/external/Sigminer
SIGMINER_BUILD ?= $(SIGMINER_DIR)/build
SIGMINER_LIB = $(SIGMINER_BUILD)/libsigminer.a
SIGMINER_CPPFLAGS = -I$(SIGMINER_DIR)/include
SIGMINER_LDFLAGS = -L/usr/lib/llvm-14/lib -lLLVM-14 -lstdc++
bt_gen$(T.exe): $(TOP)/tool/bt_gen.c $(SIGMINER_LIB)
$(CXX) $(T.cc.sqlite.extras) $(SIGMINER_CPPFLAGS) -o $@ \
$(TOP)/tool/bt_gen.c $(SIGMINER_LIB) $(SIGMINER_LDFLAGS)
$(SIGMINER_LIB):
cmake -S $(SIGMINER_DIR) -B $(SIGMINER_BUILD) -DBUILD_TESTING=OFF
cmake --build $(SIGMINER_BUILD)
xbin: bt_gen$(T.exe)
For the SQLite integration, we did not try to merge Sigminer into SQLite's normal C build rules or core library. Instead, we added a new standalone tool target to main.mk and linked that tool against a separately built libsigminer.a.
CXX ?= c++ was added because SQLite's build is primarily C-oriented, but Sigminer itself is implemented in C++. Even though bt_gen is a C source file using the C ABI, the final executable still links against a C++ static library, so using the C++ linker driver is the safest choice.
SIGMINER_DIR ?= $(TOP)/external/Sigminer reflects the layout we used in this codebase. Sigminer was vendored under external/, so we point the build at that in-tree copy rather than assuming a system install or sibling checkout. SIGMINER_BUILD ?= $(SIGMINER_DIR)/build keeps Sigminer's CMake build artifacts contained inside its own subtree.
SIGMINER_LIB = $(SIGMINER_BUILD)/libsigminer.a names the actual static library produced by the Sigminer build. SQLite's new tool target depends on that archive, so make bt_gen will first ensure Sigminer has been built.
SIGMINER_CPPFLAGS = -I$(SIGMINER_DIR)/include adds Sigminer's public headers to the compile step. Even though the new tool is written in C, it still needs access to sigminer/sigminer_c.h, which lives under include/.
SIGMINER_LDFLAGS = -L/usr/lib/llvm-14/lib -lLLVM-14 -lstdc++ was necessary because linking libsigminer.a alone is not enough. Sigminer depends on LLVM and is implemented in C++, so the final binary must also link the LLVM library and the C++ runtime. In this SQLite integration, we hardcoded the LLVM 14 location because that matched the local environment and avoided using GNU-make-specific $(shell ...) logic inside main.mk.
The bt_gen$(T.exe) rule adds the actual SQLite-side executable. We pointed it at $(TOP)/tool/bt_gen.c, which is the minimal C wrapper that uses Sigminer's C ABI. The recipe uses $(CXX) for the final link, includes SQLite's existing $(T.cc.sqlite.extras) flags so the target fits into the surrounding build environment, adds Sigminer's include path, then links the tool source, libsigminer.a, and the required LLVM/C++ link flags.
The $(SIGMINER_LIB) rule is what makes the nested Sigminer build happen. We invoke CMake on the vendored external/Sigminer tree and build it in place. The important customization here is -DBUILD_TESTING=OFF. Without that, Sigminer's default CMake configuration also enables its test setup, which pulls additional dependencies through FetchContent. For SQLite, we only needed the library itself, so disabling tests made the integration simpler and avoided unrelated build-time fetch behavior.
Finally, xbin: bt_gen$(T.exe) hooks the new tool into SQLite's existing "extra binaries" grouping. That makes bt_gen behave like the other standalone utilities in this tree rather than existing as an isolated one-off rule.
In short, the adjustment for SQLite was: vendor Sigminer under external/, build it as its own CMake-managed static library, consume only its public C ABI from a small C tool under tool/, and link that tool explicitly with the C++ and LLVM dependencies Sigminer requires.
With the build system squared away, we now turn our attention to the actual implementation. We simply need to write out bt_gen.c with our objectives in mind: to create a bpftrace script generator for user-space functions.
My implementation:
#include <stdio.h>
#include "sigminer/sigminer_c.h"
static const char *return_code_name(ReturnCode code)
{
switch( code ){
case RETURN_CODE_SUCCESS: return "SUCCESS";
case RETURN_CODE_INVALID_INPUT: return "INVALID_INPUT";
case RETURN_CODE_FILE_OPEN_FAILURE: return "FILE_OPEN_FAILURE";
case RETURN_CODE_SYMBOL_RESOLUTION_FAILURE: return "SYMBOL_RESOLUTION_FAILURE";
case RETURN_CODE_DWARF_UNAVAILABLE: return "DWARF_UNAVAILABLE";
case RETURN_CODE_FUNCTION_DIE_NOT_IN_RANGE: return "FUNCTION_DIE_NOT_IN_RANGE";
case RETURN_CODE_UNSUPPORTED_TYPE: return "UNSUPPORTED_TYPE";
case RETURN_CODE_INTERNAL_FAILURE: return "INTERNAL_FAILURE";
}
return "UNKNOWN";
}
int main(int argc, char **argv)
{
Result sigResult;
BpftraceProbeTarget target;
BpftraceRenderOptions opts;
const char *script;
if( argc!=3 ){
fprintf(stderr, "usage: %s <module-path> <symbol>\n", argv[0]);
return 1;
}
sigResult = SIGMINER_GetSignatureFromSharedObjectBySymbol(argv[1], argv[2]);
if( !sigResult.HasSignature ){
fprintf(stderr, "sigminer: signature lookup failed: %s\n",
return_code_name(sigResult.RetCode));
SIGMINER_FreeResult(&sigResult);
return 1;
}
target.ModulePath = argv[1];
target.Symbol = argv[2];
opts.HasPid = 0;
opts.Pid = 0;
opts.IncludeEntryProbe = 1;
opts.IncludeReturnProbe = 1;
opts.IncludeTimingMs = 0;
opts.IncludeUserStack = 0;
opts.IncludeArgumentPrinting = 1;
opts.IncludeReturnPrinting = 1;
opts.EnableRichTypePrinting = 0;
opts.MaxAggregateDepth = 0;
opts.MaxAggregateMembers = 0;
opts.MaxArrayElements = 0;
script = SIGMINER_BuildBpftraceUprobeScriptForTarget(&target, &sigResult.Sig, &opts);
if( script==0 ){
fprintf(stderr, "sigminer: failed to render bpftrace script\n");
SIGMINER_FreeResult(&sigResult);
return 1;
}
puts(script);
SIGMINER_FreeCString(script);
SIGMINER_FreeResult(&sigResult);
return 0;
}
This script takes an executable and a function symbol as input, then generates a uprobe bpftrace script. The script is then written to standard output with puts.
Let's build and test now!
Build:
./configure
make tidy
make sqlite3 # The executable we will pass to our tool
make bt_gen
bpftrace script gen:
./bt_gen ./sqlite3 sqlite3_open > script.bt # sqlite3_open should run whenever we open a db file through sqlite3
Output script.bt:
uprobe:./sqlite3:sqlite3_open
{
printf("./sqlite3:sqlite3_open [entry]\n");
printf(" args: (char) %p (<unnamed@0x4226>) %p\n", arg0, arg1);
}
uretprobe:./sqlite3:sqlite3_open
{
printf("./sqlite3:sqlite3_open [return]\n");
$ret = (int32)retval;
printf(" retval: (int) %d\n", $ret);
}
So now that we have our script, assuring us that Sigminer has functioned correctly to give us what looks like the correct output, we will not get ahead of ourselves and put this to the test immediately.
Executing the script works, and probes seem to be attached:
$ sudo bpftrace script.bt
Attaching 2 probes...
Now, since bpftrace is monitoring packets in and out to the kernel for this user-space function symbol, we can try opening a .db file using sqlite3 to see the function be hit and a couple of entry and exit probes be printed as a result.
$ ./sqlite3 test/fuzzdata1.db
SQLite version 3.54.0 2026-05-18 14:28:53
Enter ".help" for usage hints.
SQLite-3.54 fuzzdata1.db->
And bpftrace gives us nothing... we are very nervous but have not lost hope yet. Sigminer is fairly robust, and the bpftrace script does not seem problematic in any way. Maybe we have the incorrect assumption about sqlite3_open; perhaps this function does not run in this scenario. In fact, this codebase seems to have sqlite3_open_v2. I don't know this repository well enough to ascertain which symbol is being called when opening a .db file, and I don't have to guess either. We can just re-generate our script with this new function and give it another go.
Terminal 1:
./bt_gen ./sqlite3 sqlite3_open_v2 > script.bt
$ sudo bpftrace script.bt
Attaching 2 probes...
Terminal 2:
$ ./sqlite3 test/fuzzdata1.db
SQLite version 3.54.0 2026-05-18 14:28:53
Enter ".help" for usage hints.
SQLite-3.54 fuzzdata1.db->
Terminal 1:
./sqlite3:sqlite3_open_v2 [entry]
args: (char) 0xffffc69e15a7 (<unnamed@0x4226>) 0xffffc69deca8 (int) 1 (char) (nil)
./sqlite3:sqlite3_open_v2 [return]
retval: (int) 0
./sqlite3:sqlite3_open_v2 [entry]
args: (char) 0xffffc69e15a7 (<unnamed@0x4226>) 0xffffc69dec58 (int) 0 (char) (nil)
./sqlite3:sqlite3_open_v2 [return]
retval: (int) 21
./sqlite3:sqlite3_open_v2 [entry]
args: (char) 0xffffc69e15a7 (<unnamed@0x4226>) 0xffffc69dee98 (int) 6 (char) (nil)
./sqlite3:sqlite3_open_v2 [return]
retval: (int) 0
Aha! Our assumption was correct, and it turned out to be very inexpensive to test in our little scenario here.
In the end, we managed to vendor Sigminer into the SQLite tree, wire it into the existing build system through a standalone tool target, and produce a working bpftrace script generator without disturbing SQLite's core C library. The integration pattern we used—keeping Sigminer's CMake build separate, consuming only its C ABI, and linking the final tool against LLVM and the C++ runtime—kept the changes surgical and easy to reason about. The brief confusion over sqlite3_open versus sqlite3_open_v2 was a good reminder that even when the tooling is solid, the target binary still holds the final truth. Being able to iterate quickly with bt_gen made the correction painless, and we walked away with a clean, reproducible workflow for mining function signatures and turning them into live probes.