cudaarithm: bitwise_and/or/xor/not on a GpuMat ROI whose data pointer is not 4-byte (2-byte) aligned fail with a sticky "misaligned address" CUDA error
System information (version)
- OpenCV => 5.x, opencv f8ef5fd (2026-09-06) + opencv_contrib 17af220 (2026-08-14), built from source with CUDA 12.6, CUDA_ARCH_BIN=8.7
- Operating System / Platform => Ubuntu 22.04.5 aarch64 (NVIDIA Jetson Orin Nano, JetPack 6.2.3); only this device was available, but nothing in the code path depends on the device
- Compiler => GCC 11.4.0, nvcc 12.6
Detailed description
cv::cuda::bitwise_and / bitwise_or / bitwise_xor / bitwise_not without a mask pick the kernel from the row width in bytes only. In modules/cudaarithm/src/cuda/bitwise_mat.cu the binary ops do (lines 182-190; bitwise_not has the same structure at lines 78-104, with uint pointers at 84-85 and ushort pointers at 93-94):
const int bcols = (int) (src1.cols * src1.elemSize());
if ((bcols & 3) == 0)
{
const int vcols = bcols >> 2;
GpuMat vsrc1(src1.rows, vcols, CV_32SC1, src1.data, src1.step);
GpuMat vsrc2(src1.rows, vcols, CV_32SC1, src2.data, src2.step);
GpuMat vdst(src1.rows, vcols, CV_32SC1, dst.data, dst.step);and fall back to 16-bit words when bcols % 2 == 0 and to bytes otherwise. src1.data, src2.data and dst.data are not checked. For a ROI of a larger GpuMat whose x offset in bytes is not a multiple of 4 (or of 2 for the 16-bit path) the loads are misaligned and the kernel fails with misaligned address. The error is sticky: every later CUDA call that touches the context fails as well, so this takes the whole application down, not just the one call.
What fails, from the run below (one process per trial, ROI width w and x offset xoff in elements, data%4 = the ROI data pointer modulo 4):
- 8UC1, width 16 (16 bytes, 32-bit path): x offsets 1, 2, 3 and 5 fail, 0 and 4 work; width 18 (16-bit path): odd offsets fail; width 17 and 113 (odd byte width, byte path): every offset works; width 450: odd offsets fail.
- 8UC3, width 16 (48 bytes): offsets 1, 2, 3, 5 fail. 16UC1, widths 16, 18 and 450: offsets 1, 3, 5 (
data%4 == 2) fail; width 17 and 113 work. - 8UC4, 32SC1 and 32FC1: never fail (the element size keeps the pointer 4-byte aligned).
bitwise_notbehaves exactly like the binary ops.cv::cuda::addon the same ROIs works. With a mask a kernel of the element depth is used and every offset works (bitwise_and/or/xoraccept a mask for single-channel input only).- The destination matters as well: with two aligned sources and a destination that is a ROI at x offset 1, 2, 3 or 5,
bitwise_andfails the same way (second program below).
The tests do not cover this: Bitwise_Array in modules/cudaarithm/test/test_element_operations.cpp (lines 2044-2087) uploads with plain loadMat(src) and is not instantiated over WHOLE_SUBMAT, so the kernels never see a ROI there.
Suggested fix: either take the 32-bit / 16-bit path only when the data pointers of all three operands are aligned accordingly and otherwise use the byte path that already exists for odd widths (this is what modules/cudaarithm/src/cuda/add_mat.cu does for its own reinterpreting paths: isAllAligned, lines 194-215, requires the three data pointers to be 32-byte aligned, which is why cv::cuda::add works on the same ROIs), or handle the unaligned head and tail of each row inside the kernel, as cuda::calcHist was changed to do for the same class of bug (#3473, PR #3475).
Steps to reproduce
One trial per process (the error is sticky); the driver loop and the compile line are given after the program.
// repro_D_cuda_bitwise_misaligned_roi.cpp — cv::cuda::bitwise_{and,or,xor,not} on a GpuMat ROI whose data
// pointer is not 4-byte aligned (x offset of a few elements): one trial per process, because the CUDA
// "misaligned address" error is sticky. usage: repro_D <op> <type> <width> <xoff> [mask]
#include <opencv2/core.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/cudaarithm.hpp>
#include <cstdio>
#include <cstring>
#include <string>
static int typeFrom(const char* s) {
static const struct { const char* n; int t; } T[] = {{"8UC1", CV_8UC1}, {"8UC3", CV_8UC3}, {"8UC4", CV_8UC4}, {"16UC1", CV_16UC1}, {"16UC2", CV_16UC2}, {"32SC1", CV_32SC1}, {"32FC1", CV_32FC1}, {"64FC1", CV_64FC1}};
for (auto& e : T) if (!strcmp(e.n, s)) return e.t; return -1;
}
int main(int argc, char** argv) {
if (argc < 5) { fprintf(stderr, "usage: repro_D <and|or|xor|not|add> <type> <width> <xoff> [mask]\n"); return 2; }
std::string op = argv[1]; int t = typeFrom(argv[2]), w = atoi(argv[3]), xoff = atoi(argv[4]); bool mask = argc > 5; int h = 8;
cv::Mat A(h, w + xoff + 3, t), B(h, w + xoff + 3, t); cv::randu(A, 0, 255); cv::randu(B, 0, 255);
cv::cuda::GpuMat gA(A), gB(B), d, gm; cv::cuda::GpuMat a = gA(cv::Rect(xoff, 0, w, h)), b = gB(cv::Rect(xoff, 0, w, h));
cv::Mat ha = A(cv::Rect(xoff, 0, w, h)), hb = B(cv::Rect(xoff, 0, w, h)), ref, m;
if (mask) { m = cv::Mat(h, w, CV_8UC1); cv::randu(m, 0, 2); m *= 255; gm.upload(m); ref = cv::Mat::zeros(h, w, t); }
printf("%-4s %-6s w=%-4d xoff=%d elemSize=%zu step=%zu data%%4=%zu mask=%d : ", op.c_str(), argv[2], w, xoff, a.elemSize(), a.step, (size_t)a.data % 4, (int)mask);
fflush(stdout);
try {
cv::_InputArray gmask = mask ? cv::_InputArray(gm) : cv::_InputArray(), hmask = mask ? cv::_InputArray(m) : cv::_InputArray();
if (mask) d.upload(ref);
if (op == "and") { cv::cuda::bitwise_and(a, b, d, gmask); cv::bitwise_and(ha, hb, ref, hmask); }
else if (op == "or") { cv::cuda::bitwise_or(a, b, d, gmask); cv::bitwise_or(ha, hb, ref, hmask); }
else if (op == "xor") { cv::cuda::bitwise_xor(a, b, d, gmask); cv::bitwise_xor(ha, hb, ref, hmask); }
else if (op == "not") { cv::cuda::bitwise_not(a, d, gmask); cv::bitwise_not(ha, ref, hmask); }
else if (op == "add") { cv::cuda::add(a, b, d, gmask); cv::add(ha, hb, ref, hmask); }
else { printf("unknown op\n"); return 2; }
cv::Mat hd; d.download(hd);
printf("ok max|gpu-cpu|=%g\n", cv::norm(hd, ref, cv::NORM_INF));
} catch (const cv::Exception& e) { std::string s = e.err; for (auto& c : s) if (c == '\n') c = ' '; printf("EXC %s\n", s.c_str()); return 1; }
return 0;
}Built with g++ -std=c++17 -O2 repro_D.cpp -o repro_D -I<build> -I<opencv>/include -I<opencv>/modules/core/include -I<contrib>/modules/cudaarithm/include -I<contrib>/modules/cudev/include -I/usr/local/cuda/include -L<build>/lib -lopencv_cudaarithm -lopencv_core, run as for op in and or xor not add; do for ty in 8UC1 8UC3 8UC4 16UC1 32SC1 32FC1; do for w in 16 17 18 113 450; do for xo in 0 1 2 3 4 5; do ./repro_D $op $ty $w $xo; done; done; done; done (900 trials), followed by for op in and not; do for ty in 8UC1 16UC1; do for xo in 0 1 2 5; do ./repro_D $op $ty 16 $xo mask; done; done; done (16 trials with a mask). Lines from those runs (every line is verbatim; the other operators, types and widths follow the pattern described above):
and 8UC1 w=16 xoff=0 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=16 xoff=1 elemSize=1 step=512 data%4=1 mask=0 : EXC misaligned address
and 8UC1 w=16 xoff=2 elemSize=1 step=512 data%4=2 mask=0 : EXC misaligned address
and 8UC1 w=16 xoff=3 elemSize=1 step=512 data%4=3 mask=0 : EXC misaligned address
and 8UC1 w=16 xoff=4 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=16 xoff=5 elemSize=1 step=512 data%4=1 mask=0 : EXC misaligned address
and 8UC1 w=17 xoff=0 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=17 xoff=1 elemSize=1 step=512 data%4=1 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=17 xoff=2 elemSize=1 step=512 data%4=2 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=17 xoff=3 elemSize=1 step=512 data%4=3 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=17 xoff=4 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=17 xoff=5 elemSize=1 step=512 data%4=1 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=18 xoff=0 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=18 xoff=1 elemSize=1 step=512 data%4=1 mask=0 : EXC misaligned address
and 8UC1 w=18 xoff=2 elemSize=1 step=512 data%4=2 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=18 xoff=3 elemSize=1 step=512 data%4=3 mask=0 : EXC misaligned address
and 8UC1 w=18 xoff=4 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=18 xoff=5 elemSize=1 step=512 data%4=1 mask=0 : EXC misaligned address
and 8UC4 w=16 xoff=0 elemSize=4 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC4 w=16 xoff=1 elemSize=4 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC4 w=16 xoff=2 elemSize=4 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC4 w=16 xoff=3 elemSize=4 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC4 w=16 xoff=4 elemSize=4 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 8UC4 w=16 xoff=5 elemSize=4 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 16UC1 w=16 xoff=0 elemSize=2 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 16UC1 w=16 xoff=1 elemSize=2 step=512 data%4=2 mask=0 : EXC misaligned address
and 16UC1 w=16 xoff=2 elemSize=2 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 16UC1 w=16 xoff=3 elemSize=2 step=512 data%4=2 mask=0 : EXC misaligned address
and 16UC1 w=16 xoff=4 elemSize=2 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
and 16UC1 w=16 xoff=5 elemSize=2 step=512 data%4=2 mask=0 : EXC misaligned address
not 8UC1 w=16 xoff=0 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
not 8UC1 w=16 xoff=1 elemSize=1 step=512 data%4=1 mask=0 : EXC misaligned address
not 8UC1 w=16 xoff=2 elemSize=1 step=512 data%4=2 mask=0 : EXC misaligned address
not 8UC1 w=16 xoff=3 elemSize=1 step=512 data%4=3 mask=0 : EXC misaligned address
not 8UC1 w=16 xoff=4 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
not 8UC1 w=16 xoff=5 elemSize=1 step=512 data%4=1 mask=0 : EXC misaligned address
add 8UC1 w=16 xoff=0 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
add 8UC1 w=16 xoff=1 elemSize=1 step=512 data%4=1 mask=0 : ok max|gpu-cpu|=0
add 8UC1 w=16 xoff=2 elemSize=1 step=512 data%4=2 mask=0 : ok max|gpu-cpu|=0
add 8UC1 w=16 xoff=3 elemSize=1 step=512 data%4=3 mask=0 : ok max|gpu-cpu|=0
add 8UC1 w=16 xoff=4 elemSize=1 step=512 data%4=0 mask=0 : ok max|gpu-cpu|=0
add 8UC1 w=16 xoff=5 elemSize=1 step=512 data%4=1 mask=0 : ok max|gpu-cpu|=0
and 8UC1 w=16 xoff=0 elemSize=1 step=512 data%4=0 mask=1 : ok max|gpu-cpu|=0
and 8UC1 w=16 xoff=1 elemSize=1 step=512 data%4=1 mask=1 : ok max|gpu-cpu|=0
and 8UC1 w=16 xoff=2 elemSize=1 step=512 data%4=2 mask=1 : ok max|gpu-cpu|=0
and 8UC1 w=16 xoff=5 elemSize=1 step=512 data%4=1 mask=1 : ok max|gpu-cpu|=0Destination ROI (repro_D2, below, built the same way; for xo in 0 1 2 3 4 5; do ./repro_D2 $xo; done):
// repro_D2_cuda_bitwise_misaligned_dst_roi.cpp — cv::cuda::bitwise_and with two ALIGNED 8UC1 sources (width 16)
// and a DESTINATION that is a ROI of a larger GpuMat at x offset <xoff>: one trial per process.
// usage: repro_D2 <xoff>
#include <opencv2/core.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/cudaarithm.hpp>
#include <cstdio>
#include <cstdlib>
int main(int argc, char** argv) {
int xoff = argc > 1 ? atoi(argv[1]) : 1, w = 16, h = 8;
cv::Mat A(h, w, CV_8UC1), B(h, w, CV_8UC1); cv::randu(A, 0, 255); cv::randu(B, 0, 255);
cv::cuda::GpuMat a(A), b(B), dparent(h, w + xoff + 3, CV_8UC1, cv::Scalar(0)); cv::cuda::GpuMat d = dparent(cv::Rect(xoff, 0, w, h));
printf("dst ROI xoff=%d src.data%%4=%zu,%zu dst.data%%4=%zu : ", xoff, (size_t)a.data % 4, (size_t)b.data % 4, (size_t)d.data % 4); fflush(stdout);
try { cv::cuda::bitwise_and(a, b, d); cv::Mat ref, got; cv::bitwise_and(A, B, ref); d.download(got); printf("ok max|gpu-cpu|=%g\n", cv::norm(got, ref, cv::NORM_INF)); }
catch (const cv::Exception& e) { std::string s = e.err; for (auto& c : s) if (c == '\n') c = ' '; printf("EXC %s\n", s.c_str()); return 1; }
return 0;
}dst ROI xoff=0 src.data%4=0,0 dst.data%4=0 : ok max|gpu-cpu|=0
dst ROI xoff=1 src.data%4=0,0 dst.data%4=1 : EXC misaligned address
dst ROI xoff=2 src.data%4=0,0 dst.data%4=2 : EXC misaligned address
dst ROI xoff=3 src.data%4=0,0 dst.data%4=3 : EXC misaligned address
dst ROI xoff=4 src.data%4=0,0 dst.data%4=0 : ok max|gpu-cpu|=0
dst ROI xoff=5 src.data%4=0,0 dst.data%4=1 : EXC misaligned addressI ran into this with a GPU-vs-CPU sweep that uses ROI inputs at x offset 5: its bitwise_and / bitwise_or / bitwise_xor cases on 8UC1, 8UC3, 8SC1, 16UC1 and 16SC1 ROIs of even width (90 cases) poisoned the sweep's CUDA context, while the 8UC4 and 32-bit cases and the odd widths did not.
Note: the investigation and this report were prepared with an AI assistant; all runs are from my own board and the output blocks are the programs' own output, pasted line for line.
Issue submission checklist
- I report the issue, it's not a question
- I checked the problem with documentation, FAQ, open issues, forum.opencv.org, Stack Overflow, etc and have not found any solution
- I updated to the latest OpenCV version and the issue is still there
- There is reproducer code and related data files: videos, images, onnx, etc
Source: opencv/opencv_contrib