cgccc11sanitizer

libzimg: AddressSanitizer: SEGV


I have a collection of JPEG files that I would like to scale down by a specific size with zimg (aka z.lib) library:

This is my code snippet:

#include <cstdio> // fopen, fseek, ftell, fread, fwrite
#include <cstdint> // uint8_t
#include <cstdlib> // aligned_alloc, free
#include <cstring> // strlen, strtok, strcat
#include <cerrno> // errno

#include <jpeglib.h> // jpeg compression/decompression routines

#include <zimg.h> // colorspace and scaling routines

static void print_zimg_error(const char *function_name)
{
    char err_msg[1024];
    int err_code = zimg_get_last_error(err_msg, sizeof(err_msg));
    if (err_code != ZIMG_ERROR_SUCCESS)
        fprintf(stderr, "%s(): %s\n", function_name, err_msg);

    zimg_clear_last_error();

    return;
}

static int width_to_stride(int w)
{
    return ((w + 31) & ~31);
}

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "usage: %s <filename>.JPG\n", argv[0]);
        return 1;
    }

    const char *src_name = argv[1];
    char *src_name_2 = strdup(src_name);
    const char *delim = "/";
    char *tok;
    char *dst_name;

    strtok(src_name_2, delim);
    while ((tok = strtok(NULL, delim)) != NULL)
        dst_name = tok;

    dst_name = strcat(strtok(dst_name, "."), ".JPG");

    // Define input sizes
    int src_width = 5152;
    int src_height = 3864;
    int src_stride = width_to_stride(src_width) + (width_to_stride(src_width / 2) * 2);
    int src_pixel_count = (src_stride * src_height) + (src_stride * (src_height / 2)) + (src_stride * (src_height / 2));

    // Define output sizes
    int dst_width = 2560;
    int dst_height = 1400;
    int dst_stride = width_to_stride(dst_width) + (width_to_stride(dst_width / 2) * 2);
    int dst_pixel_count = (dst_stride * dst_height) + (dst_stride * (dst_height / 2)) + (dst_stride * (dst_height / 2));

    uint8_t *src_jpeg_buf = (uint8_t *) aligned_alloc(32, ((src_pixel_count) + 32 - 1) & ~(32 - 1));
    if (src_jpeg_buf == NULL) {
        fprintf(stderr, "aligned_alloc(): src_jpeg_buf is NULL\n");
        return 1;
    }
    uint8_t *dst_jpeg_buf = (uint8_t *) aligned_alloc(32, ((dst_pixel_count) + 32 - 1) & ~(32 - 1));
    if (dst_jpeg_buf == NULL) {
        fprintf(stderr, "aligned_alloc(): dst_jpeg_buf is NULL\n");
        return 1;
    }

    FILE *src_file = fopen(src_name, "rb");
    if (src_file == NULL) {
        perror("fopen()");
        return 1;
    }

    struct jpeg_decompress_struct jpeg_dec;
    struct jpeg_error_mgr jpeg_err;

    jpeg_dec.err = jpeg_std_error(&jpeg_err);
    jpeg_create_decompress(&jpeg_dec);

    jpeg_stdio_src(&jpeg_dec, src_file);

    jpeg_read_header(&jpeg_dec, TRUE);

    jpeg_start_decompress(&jpeg_dec);

    int row_stride = jpeg_dec.output_width * jpeg_dec.output_components;
    JSAMPARRAY scanline_buffer = (*jpeg_dec.mem->alloc_sarray)((j_common_ptr) &jpeg_dec, JPOOL_IMAGE, row_stride, 1);

    while (jpeg_dec.output_scanline < jpeg_dec.output_height) {
        jpeg_read_scanlines(&jpeg_dec, scanline_buffer, 1);
        memcpy(src_jpeg_buf + (jpeg_dec.output_scanline - 1) * row_stride, scanline_buffer[0], row_stride);
    }

    jpeg_finish_decompress(&jpeg_dec);
    jpeg_destroy_decompress(&jpeg_dec);

    fclose(src_file);

    // Initialize zimg structures to defaults
    zimg_filter_graph *graph = 0;
    zimg_image_buffer_const src_buf = { ZIMG_API_VERSION }; 
    zimg_image_buffer dst_buf = { ZIMG_API_VERSION }; 
    zimg_image_format src_format;
    zimg_image_format dst_format;
    zimg_graph_builder_params params;
    size_t tmp_size;
    void *tmp = 0;
    
    zimg_image_format_default(&src_format, ZIMG_API_VERSION); 
    print_zimg_error("zimg_image_format_default");

    zimg_image_format_default(&dst_format, ZIMG_API_VERSION); 
    print_zimg_error("zimg_image_format_default");

    zimg_graph_builder_params_default(&params, ZIMG_API_VERSION);
    params.resample_filter = ZIMG_RESIZE_BICUBIC;
    params.filter_param_a = -0.5;
    params.filter_param_b = 0.25;
    params.resample_filter_uv = ZIMG_RESIZE_BICUBIC;
    params.filter_param_a_uv = -0.5;
    params.filter_param_b_uv = 0.25;

    src_format.width = src_width;
    src_format.height = src_height;
    src_format.pixel_type = ZIMG_PIXEL_BYTE;

    src_format.subsample_w = 1;
    src_format.subsample_h = 0;

    src_format.color_family = ZIMG_COLOR_YUV;

    dst_format.width = dst_width;
    dst_format.height = dst_height;
    dst_format.pixel_type = ZIMG_PIXEL_BYTE;

    dst_format.subsample_w = 1;
    dst_format.subsample_h = 1;

    dst_format.color_family = ZIMG_COLOR_YUV;

    graph = zimg_filter_graph_build(&src_format, &dst_format, &params);
    if (graph == NULL) {
        fprintf(stderr, "zimg_filter_graph_build(): graph is NULL\n");
        return 1;
    }
    print_zimg_error("zimg_filter_graph_build");

    zimg_filter_graph_get_tmp_size(graph, &tmp_size);
    print_zimg_error("zimg_filter_graph_get_tmp_size");

    tmp = aligned_alloc(32, tmp_size);
    if (tmp == NULL) {
        fprintf(stderr, "aligned_alloc(): tmp is NULL\n");
        return 1;
    }

    src_buf.plane[0].data = src_jpeg_buf;
    src_buf.plane[0].stride = src_stride;
    src_buf.plane[0].mask = ZIMG_BUFFER_MAX;

    src_buf.plane[0].data = dst_jpeg_buf;
    src_buf.plane[0].stride = dst_stride;
    src_buf.plane[0].mask = ZIMG_BUFFER_MAX;

    zimg_filter_graph_process(graph, &src_buf, &dst_buf, tmp, 0, 0, 0, 0);
    print_zimg_error("zimg_filter_graph_process");

    zimg_filter_graph_free(graph);
    print_zimg_error("zimg_filter_graph_free");

    free(tmp);

    FILE *dst_file = fopen(dst_name, "wb");
    fwrite(dst_jpeg_buf, dst_pixel_count, 1, dst_file);
    fclose(dst_file);

    free(src_jpeg_buf);
    free(dst_jpeg_buf);

    return 0;
}

but I get this severity error from the terminal:

==136680==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7e3914dcd808 bp 0x7fff56f8efa0 sp 0x7fff56f8de00 T0)
==136680==The signal is caused by a WRITE memory access.
==136680==Hint: address points to the zero page.
    #0 0x7e3914dcd808 in _mm_storeu_si128(long long __vector(2)*, long long __vector(2)) /usr/lib/gcc/x86_64-linux-gnu/11/include/emmintrin.h:739
    #1 0x7e3914dcd808 in store16 src/zimg/depth/x86/dither_avx2.cpp:66
    #2 0x7e3914dcd808 in ordered_dither_avx2_impl<zimg::depth::(anonymous namespace)::LoadU16, zimg::depth::(anonymous namespace)::StoreU8> src/zimg/depth/x86/dither_avx2.cpp:149
    #3 0x7e3914dcd808 in zimg::depth::ordered_dither_w2b_avx2(float const*, unsigned int, unsigned int, void const*, void*, float, float, unsigned int, unsigned int, unsigned int) src/zimg/depth/x86/dither_avx2.cpp:178
    #4 0x7e3914c7f666 in process src/zimg/depth/dither.cpp:281
    #5 0x7e3914c4d544 in process graphengine/graphengine/node.cpp:635
    #6 0x7e3914c3cdde  (/usr/local/lib/libzimg.so.2+0x3cdde)
    #7 0x7e3914c3e67d  (/usr/local/lib/libzimg.so.2+0x3e67d)
    #8 0x7e3914c39566 in graphengine::zimg::GraphImpl::run(graphengine::Graph::Endpoint const*, void*) const graphengine/graphengine/graph.cpp:877
    #9 0x7e3914c8706d in zimg::graph::FilterGraph::process(std::array<graphengine::BufferDescriptor, 4ul> const&, std::array<graphengine::BufferDescriptor, 4ul> const&, void*, int (*)(void*, unsigned int, unsigned int, unsigned int), void*, int (*)(void*, unsigned int, unsigned int, unsigned int), void*) const src/zimg/graph/filtergraph.cpp:85
    #10 0x7e3914c536d1 in zimg_filter_graph_process src/zimg/api/zimg.cpp:645
    #11 0x5b5fabb4b8e6 in main /home/gianluca/Informatica/Programmazione/C/aws-lambda-cpp-samples/aws-lambda-jpeg-zimg/test.c:167
    #12 0x7e3914429d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #13 0x7e3914429e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #14 0x5b5fabb4a544 in _start (/home/gianluca/Informatica/Programmazione/C/aws-lambda-cpp-samples/aws-lambda-jpeg-zimg/test+0x2544)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /usr/lib/gcc/x86_64-linux-gnu/11/include/emmintrin.h:739 in _mm_storeu_si128(long long __vector(2)*, long long __vector(2))
==136680==ABORTING

Code snippet was compiled with:

g++ -Wall -Werror -fsanitize=address -g -std=gnu++11 -o test test.cpp $(pkg-config --cflags --libs libjpeg zimg)

and run with ./test <filename>

Any suggestion ?

Gianluca

PS: I need to write this sentence because my post is mostly code...


Solution

  • First, based on the C example and makefile for the examples in zimg, you can write C as normal (#include <stdio.h>) and compile with gcc; you just have to make sure to link using g++, e.g.:

    CFLAGS = -Wall -Werror -g
    test: test.o
        g++ test.o -pthread $$(pkg-config --libs libjpeg zimg) -o test
    test.o: test.c
        gcc -std=c11 $(CFLAGS) -pthread $$(pkg-config --cflags libjpeg zimg) -c test.c
    

    Now, the primary problem with the code is that reading and copying the scanlines returned via libjpeg produces the following pixel layout in memory:

    scanline 1
    R G B R G B R G B ...
    scanline 2
    R G B R G B R G B ...
    scanline 3
    R G B R G B R G B ...
    ...
    

    On the other hand, zimg expects to work with each channel separately, that is:

    buf.plane[0]:
    R R R R R R R R ...
    R R R R R R R R ...
    ...
    buf.plane[1]:
    G G G G G G G G ...
    G G G G G G G G ...
    ...
    buf.plane[2]:
    B B B B B B B B ...
    B B B B B B B B ...
    ...
    

    Where each channel line is 32-byte aligned. So, rather than copying each scanline while reading the JPEG, you should extract each pixel component into the corresponding zimg plane.

    Additionally, you decompressed the input JPEG and wrote the pixels as-is to the output file. To write a JPEG, you need to use libjpeg to compress again. After scaling with zimg, the pixels can be reassembled back into RGB scanlines for use with the compressor.

    Putting this all together:

    #include <errno.h>
    #include <setjmp.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #include <jpeglib.h>
    #include <zimg.h>
    
    static JDIMENSION dimension_arg(const char *arg) {
        long value;
        char *end;
        errno = 0;
        value = strtol(arg, &end, 10);
        if (errno != 0 || end == arg || *end != '\0' || value < 1 || value > JPEG_MAX_DIMENSION) {
            (void)fprintf(
                stderr, "Invalid JPEG dimension (range [1-%ld]): %s\n", JPEG_MAX_DIMENSION, arg
            );
            return 0;
        }
        return value;
    }
    
    struct image {
        struct zimg_image_format format;
        uint8_t *data;
        uint8_t *plane[3];
        unsigned int stride;
    };
    
    #define PAD32(x) (((x) + 0x1f) & ~0x1f)
    
    static int image_init(struct image *image, unsigned int width, unsigned int height) {
        zimg_image_format_default(&image->format, ZIMG_API_VERSION);
        image->format.width = width;
        image->format.height = height;
        image->format.pixel_type = ZIMG_PIXEL_BYTE;
        image->format.color_family = ZIMG_COLOR_RGB;
        image->format.pixel_range = ZIMG_RANGE_FULL;
    
        image->stride = PAD32(width);
        size_t plane_size = (size_t)image->stride * height;
        image->data = aligned_alloc(32, plane_size * 3);
        if (image->data == NULL) {
            (void)fputs("Failed to allocate image memory\n", stderr);
            return 0;
        }
        for (int i = 0; i < 3; ++i)
            image->plane[i] = image->data + plane_size * i;
    
        return 1;
    }
    
    static void image_free(struct image *image) {
        free(image->data);
    }
    
    static void handle_jpeg_error(j_common_ptr cinfo) {
        (*cinfo->err->output_message)(cinfo);
        jmp_buf *env = cinfo->client_data;
        jpeg_destroy(cinfo);
        longjmp(*env, 1);
    }
    
    static int read_jpeg(const char *path, struct image *image) {
        FILE *file = fopen(path, "rb");
        if (file == NULL) {
            (void)fprintf(stderr, "Failed to open input JPEG %s: %s\n", path, strerror(errno));
            return 0;
        }
    
        struct jpeg_decompress_struct cinfo;
        struct jpeg_error_mgr jerr;
    
        cinfo.err = jpeg_std_error(&jerr);
        jerr.error_exit = handle_jpeg_error;
    
        volatile int allocated_image = 0;
        jmp_buf env;
        cinfo.client_data = &env;
        if (setjmp(env)) {
            // Fatal libjpeg error; we've already destroyed cinfo.
            (void)fclose(file);
            if (allocated_image)
                image_free(image);
            return 0;
        }
    
        jpeg_create_decompress(&cinfo);
        // We are using a non-suspending data source (stdio), so the return value of read_header,
        // start_decompress, and finish_decompress can be ignored.
        jpeg_stdio_src(&cinfo, file);
    
        (void)jpeg_read_header(&cinfo, 1);
    #ifdef DEBUG
        fprintf(
            stderr, "Input JPEG: dim=%ux%u, space=%d, components=%d\n",
            cinfo.image_width, cinfo.image_height, cinfo.jpeg_color_space, cinfo.num_components
        );
    #endif
        // Always decompress to RGB. This means we will have 3 output components.
        cinfo.out_color_space = JCS_RGB;
    
        // Start decompress: fills out cinfo.out*.
        (void)jpeg_start_decompress(&cinfo);
    
        if (!image_init(image, cinfo.output_width, cinfo.output_height))
            goto err_destroy_jpeg;
        allocated_image = 1;
    
        // libjpeg-managed scanline buffer, freed on cinfo destroy. Assume cinfo.rec_outbuf_height is
        // just 1 line since we use the default decompression.
        JDIMENSION scan_size = cinfo.output_width * sizeof(JSAMPLE) * 3;
        JSAMPARRAY scan_buf =
            (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, scan_size, 1);
    
        size_t line_start = 0;
        while (cinfo.output_scanline < cinfo.output_height) {
            (void)jpeg_read_scanlines(&cinfo, scan_buf, 1);
            JSAMPROW p = scan_buf[0];
            for (JDIMENSION i = 0; i < cinfo.output_width; ++i) {
                image->plane[0][line_start + i] = p[0];
                image->plane[1][line_start + i] = p[1];
                image->plane[2][line_start + i] = p[2];
                p += 3;
            }
            line_start += image->stride;
        }
    
        (void)jpeg_finish_decompress(&cinfo);
        jpeg_destroy_decompress(&cinfo);
        (void)fclose(file);
        return 1;
    
    err_destroy_jpeg:
        jpeg_destroy_decompress(&cinfo);
        (void)fclose(file);
        return 0;
    }
    
    static int write_jpeg(const char *path, struct image *image) {
        FILE *file = fopen(path, "wb");
        if (file == NULL) {
            (void)fprintf(stderr, "Failed to open path for writing %s: %s\n", path, strerror(errno));
            return 0;
        }
    
        // See comments in read_jpeg for libjpeg operation details.
        struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;
    
        cinfo.err = jpeg_std_error(&jerr);
        jerr.error_exit = handle_jpeg_error;
    
        jmp_buf env;
        cinfo.client_data = &env;
        if (setjmp(env)) {
            (void)fclose(file);
            return 0;
        }
    
        jpeg_create_compress(&cinfo);
        jpeg_stdio_dest(&cinfo, file);
    
        cinfo.image_width = image->format.width;
        cinfo.image_height = image->format.height;
        cinfo.input_components = 3;
        cinfo.in_color_space = JCS_RGB;
    
        jpeg_set_defaults(&cinfo);
        jpeg_set_quality(&cinfo, 80, 1);
    
        jpeg_start_compress(&cinfo, 1);
    
        JDIMENSION scan_size = cinfo.image_width * sizeof(JSAMPLE) * 3;
        JSAMPARRAY scan_buf =
            (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, scan_size, 1);
    
        size_t line_start = 0;
        while (cinfo.next_scanline < cinfo.image_height) {
            JSAMPROW p = scan_buf[0];
            for (JDIMENSION i = 0; i < cinfo.image_width; ++i) {
                p[0] = image->plane[0][line_start + i];
                p[1] = image->plane[1][line_start + i];
                p[2] = image->plane[2][line_start + i];
                p += 3;
            }
            (void)jpeg_write_scanlines(&cinfo, scan_buf, 1);
            line_start += image->stride;
        }
    
        jpeg_finish_compress(&cinfo);
        jpeg_destroy_compress(&cinfo);
        if (fclose(file) == EOF) {
            (void)fprintf(stderr, "Failed to finish writing %s: %s\n", path, strerror(errno));
            return 0;
        }
    
        return 1;
    }
    
    static void output_zimg_error(const char *func) {
        char buf[1024];
        enum zimg_error_code_e zerr = zimg_get_last_error(buf, sizeof(buf));
        (void)fprintf(stderr, "%s: error %d: %s\n", func, zerr, buf);
    }
    
    static int scale(struct image *in, struct image *out) {
        struct zimg_graph_builder_params params;
        zimg_graph_builder_params_default(&params, ZIMG_API_VERSION);
        params.resample_filter = ZIMG_RESIZE_LANCZOS;
        params.resample_filter_uv = ZIMG_RESIZE_LANCZOS;
    
        struct zimg_filter_graph *graph = zimg_filter_graph_build(&in->format, &out->format, &params);
        if (graph == NULL) {
            output_zimg_error("zimg_filter_graph_build");
            goto err;
        }
    
        enum zimg_error_code_e zerr;
        size_t tmp_size;
        zerr = zimg_filter_graph_get_tmp_size(graph, &tmp_size);
        if (zerr != ZIMG_ERROR_SUCCESS) {
            output_zimg_error("zimg_filter_graph_get_tmp_size");
            goto err_free_graph;
        }
    
        void *tmp = aligned_alloc(32, tmp_size);
        if (tmp == NULL) {
            (void)fputs("Failed to allocate memory for zimg graph processing\n", stderr);
            goto err_free_graph;
        }
    
        struct zimg_image_buffer_const src;
        struct zimg_image_buffer dst;
        src.version = dst.version = ZIMG_API_VERSION;
        for (int p = 0; p < 3; ++p) {
            src.plane[p].data = in->plane[p];
            src.plane[p].stride = in->stride;
            src.plane[p].mask = ZIMG_BUFFER_MAX;
    
            dst.plane[p].data = out->plane[p];
            dst.plane[p].stride = out->stride;
            dst.plane[p].mask = ZIMG_BUFFER_MAX;
        }
    
        zerr = zimg_filter_graph_process(graph, &src, &dst, tmp, NULL, NULL, NULL, NULL);
        if (zerr != ZIMG_ERROR_SUCCESS) {
            output_zimg_error("zimg_filter_graph_process");
            goto err_free_tmp;
        }
    
        free(tmp);
        zimg_filter_graph_free(graph);
        return 1;
    
    err_free_tmp:
        free(tmp);
    err_free_graph:
        zimg_filter_graph_free(graph);
    err:
        return 0;
    }
    
    int main(int argc, char *argv[]) {
        if (argc != 5) {
            (void)fprintf(stderr, "Usage: %s <in JPEG> <out JPEG> <out width> <out height>\n", argv[0]);
            return 2;
        }
    
        const char *in_path = argv[1];
        const char *out_path = argv[2];
        JDIMENSION out_width = dimension_arg(argv[3]);
        if (out_width == 0)
            return 2;
        JDIMENSION out_height = dimension_arg(argv[4]);
        if (out_height == 0)
            return 2;
    
        struct image out;
        if (!image_init(&out, out_width, out_height))
            goto err;
    
        struct image in;
        if (!read_jpeg(in_path, &in))
            goto err_free_out;
    
        printf("Scaling %s to %ux%u...\n", in_path, out_width, out_height);
        if (!scale(&in, &out))
            goto err_free_in;
    
        if (!write_jpeg(out_path, &out))
            goto err_free_in;
        printf("Wrote scaled image to %s\n", out_path);
    
        image_free(&in);
        image_free(&out);
    
        return 0;
    
    err_free_in:
        image_free(&in);
    err_free_out:
        image_free(&out);
    err:
        return 1;
    }