bitbank2 / JPEGDEC

An optimized JPEG decoder suitable for microcontrollers and PCs.
Apache License 2.0
406 stars 47 forks source link

seg fault for the linux example under WSL #83

Closed robtinkers closed 1 month ago

robtinkers commented 1 month ago

I downloaded the v1.6.1 release inside the Windows Subsystem for Linux on my PC, ran 'make' inside the linux folder, but the resulting 'jpegdec' crashes with a segmentation fault.

My PC is running Windows 10 (fully updated), WSL is Ubuntu 22.04.5 LTS (fully updated) on x86_64, cc is "cc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"

I do get a few warnings on compilation (I have added -Wextra here, but the seg faults still happen without):

cc -c -Wall -Wextra -O2 -I../src -D__LINUX__ main.c
In file included from main.c:11:
../src/jpeg.inl: In function ‘JPEGMakeHuffTables’:
../src/jpeg.inl:1175:37: warning: comparison of integer expressions of different signedness: ‘int’ and ‘long unsigned int’ [-Wsign-compare]
 1175 |             if (iTable * HUFF11SIZE >= sizeof(pJPEG->usHuffAC) / 2)
      |                                     ^~
../src/jpeg.inl: In function ‘JPEGDecodeMCU_P’:
../src/jpeg.inl:1825:23: warning: left shift of negative value [-Wshift-negative-value]
 1825 |     iNegative = ((-1) << pJPEG->cApproxBitsLow); // negative bit position being coded
      |                       ^~
../src/jpeg.inl: In function ‘JPEGIDCT’:
../src/jpeg.inl:2264:9: warning: unused variable ‘iCol’ [-Wunused-variable]
 2264 |     int iCol;
      |         ^~~~
../src/jpeg.inl:2263:19: warning: unused variable ‘ucColMask’ [-Wunused-variable]
 2263 |     unsigned char ucColMask;
      |                   ^~~~~~~~~
At top level:
../src/jpeg.inl:755:16: warning: ‘readFLASH’ defined but not used [-Wunused-function]
  755 | static int32_t readFLASH(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen)
      |                ^~~~~~~~~
main.c: In function ‘JPEGFilter’:
../src/jpeg.inl:1421:25: warning: ‘xmmIn’ is used uninitialized [-Wuninitialized]
 1421 |         __m128i xmmFF = _mm_cmpeq_epi8(xmmIn, xmmIn);
      |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc main.o -lm -lpthread -o jpegdec

And the segmentation faults:

rob@PC:~/JPEGDEC-1.6.1/linux$ ./jpegdec
JPEG decoder demo
options:
Run without parameters to test in-memory decoding
Pass a single filename to test decoding performance
Pass 2 filenames to convert a JPEG file to a BMP file
e.g. ./jpegdec <infile.jpg> <outfile.bmp>

Segmentation fault
rob@PC:~/JPEGDEC-1.6.1/linux$ ./jpegdec ../demo.jpg
JPEG decoder demo
options:
Run without parameters to test in-memory decoding
Pass a single filename to test decoding performance
Pass 2 filenames to convert a JPEG file to a BMP file
e.g. ./jpegdec <infile.jpg> <outfile.bmp>

Segmentation fault
rob@PC:~/JPEGDEC-1.6.1/linux$ ./jpegdec ../perf.jpg
JPEG decoder demo
options:
Run without parameters to test in-memory decoding
Pass a single filename to test decoding performance
Pass 2 filenames to convert a JPEG file to a BMP file
e.g. ./jpegdec <infile.jpg> <outfile.bmp>

Segmentation fault

A trace:

rob@PC:~/JPEGDEC-1.6.1/linux$ strace ./jpegdec ../demo.jpg
execve("./jpegdec", ["./jpegdec", "../demo.jpg"], 0x7ffc65963348 /* 26 vars */) = 0
--- DELETED ---
openat(AT_FDCWD, "../demo.jpg", O_RDWR) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=158501, ...}, AT_EMPTY_PATH) = 0
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=158501, ...}, AT_EMPTY_PATH) = 0
lseek(3, 155648, SEEK_SET)              = 155648
read(3, "\265\2\253\335K\270\216?\201{*\367s\330\23\326\254\370\202\374qo\26B\2567\267vb~\357\276"..., 2853) = 2853
lseek(3, 0, SEEK_SET)                   = 0
read(3, "\377\330\377\340\0\20JFIF\0\1\1\0\0H\0H\0\0\377\341\2\314Exif\0\0MM"..., 4096) = 4096
--- SIGSEGV {si_signo=SIGSEGV, si_code=SI_KERNEL, si_addr=NULL} ---
+++ killed by SIGSEGV +++
Segmentation fault

But decoding does seem to work:

rob@PC:~/JPEGDEC-1.6.1/linux$ ./jpegdec ../demo.jpg demo.bmp
JPEG decoder demo
options:
Run without parameters to test in-memory decoding
Pass a single filename to test decoding performance
Pass 2 filenames to convert a JPEG file to a BMP file
e.g. ./jpegdec <infile.jpg> <outfile.bmp>

Converting 952 x 707 image, fraction = 1
JPEG decoded in 5656 us
rob@PC:~/JPEGDEC-1.6.1/linux$ ls -l demo.bmp
-rw-r--r-- 1 rob rob 1346266 Sep 24 18:08 demo.bmp
rob@PC:~/JPEGDEC-1.6.1/linux$ file demo.bmp
demo.bmp: PC bitmap, Windows 98/2000 and newer format, 952 x 707 x 16, cbSize 1346266, bits offset 138
bitbank2 commented 1 month ago

I'm sorry, I don't have an easy way to test this setup. I have MacOS and Linux and it doesn't segfault on either. Can you try adding some printf statements to narrow down where the error is?

robtinkers commented 1 month ago

In all 4 test cases (jpegdec without an argument, and also with demo.jpg / perf.jpg / squirrel_dither.jpg as an argument), the seg fault occurs when y=0 x=8 in DecodeJPEG().

Specifically, it is in the memset() near the top of JPEGDecodeMCU():

    } else { // decode all the AC coefficients
printf("JPEGDecodeMCU:memset() %p %d\n", pMCU, 64*sizeof(short));
        memset(pMCU, 0, 64*sizeof(short)); // pre-fill with zero since we may skip coefficients
printf("JPEGDecodeMCU:memset() done\n");
        pEnd2 = (uint8_t *)&cZigZag2[64];
    }

So:

$ ./jpegdec
--- DELETED ---
DecodeJPEG() y=0 x=6
JPEGDecodeMCU:pMCU initialised 0x5580dfb1a340
JPEGDecodeMCU:memset() 0x5580dfb1a340 128
JPEGDecodeMCU:memset() done
--- DELETED ---
JPEGDecodeMCU:pMCU initialised 0x5580dfb1a5c0
JPEGDecodeMCU:memset() 0x5580dfb1a5c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=7
JPEGDecodeMCU:pMCU initialised 0x5580dfb1a340
JPEGDecodeMCU:memset() 0x5580dfb1a340 128
JPEGDecodeMCU:memset() done
--- DELETED ---
JPEGDecodeMCU:pMCU initialised 0x5580dfb1a5c0
JPEGDecodeMCU:memset() 0x5580dfb1a5c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=8
JPEGDecodeMCU:pMCU initialised 0x108110a110a210c2
JPEGDecodeMCU:memset() 0x108110a110a210c2 128
Segmentation fault

And:

$ ./jpegdec ../demo.jpg
--- DELETED ---
JPEGDecodeMCU:pMCU initialised 0x56091ed455c0
JPEGDecodeMCU:memset() 0x56091ed455c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=8
JPEGDecodeMCU:pMCU initialised 0x8c708c708c508c50
JPEGDecodeMCU:memset() 0x8c708c708c508c50 128
Segmentation fault

$ ./jpegdec ../perf.jpg
--- DELETED ---
JPEGDecodeMCU:pMCU initialised 0x559bf9cc75c0
JPEGDecodeMCU:memset() 0x559bf9cc75c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=8
JPEGDecodeMCU:pMCU initialised 0xffffffffffffffff
JPEGDecodeMCU:memset() 0xffffffffffffffff 128
Segmentation fault

$ ./jpegdec ../squirrel_dither.jpg
--- DELETED ---
JPEGDecodeMCU:pMCU initialised 0x560d287f55c0
JPEGDecodeMCU:memset() 0x560d287f55c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=8
JPEGDecodeMCU:pMCU initialised 0xee95ee95ee95ee95
JPEGDecodeMCU:memset() 0xee95ee95ee95ee95 128
Segmentation fault

(The bad pMCU pointer values are consistent between runs.)

Decoding to a file still seems to work:

$ ./jpegdec ../demo.jpg demo.bmp
--- DELETED ---
JPEGDecodeMCU:memset() 0x55f5d2b035c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=8
JPEGDecodeMCU:pMCU initialised 0x55f5d2b03340
JPEGDecodeMCU:memset() 0x55f5d2b03340 128
JPEGDecodeMCU:memset() done
JPEGDecodeMCU:pMCU initialised 0x55f5d2b033c0
JPEGDecodeMCU:memset() 0x55f5d2b033c0 128
JPEGDecodeMCU:memset() done
JPEGDecodeMCU:pMCU initialised 0x55f5d2b03440
JPEGDecodeMCU:memset() 0x55f5d2b03440 128
JPEGDecodeMCU:memset() done
JPEGDecodeMCU:pMCU initialised 0x55f5d2b034c0
JPEGDecodeMCU:memset() 0x55f5d2b034c0 128
JPEGDecodeMCU:memset() done
JPEGDecodeMCU:pMCU initialised 0x55f5d2b03540
JPEGDecodeMCU:memset() 0x55f5d2b03540 128
JPEGDecodeMCU:memset() done
JPEGDecodeMCU:pMCU initialised 0x55f5d2b035c0
JPEGDecodeMCU:memset() 0x55f5d2b035c0 128
JPEGDecodeMCU:memset() done
DecodeJPEG() y=0 x=9
JPEGDecodeMCU:pMCU initialised 0x55f5d2b03340
JPEGDecodeMCU:memset() 0x55f5d2b03340 128
--- DELETED ---
$ ls -l demo.bmp
-rw-r--r-- 1 rob rob 1346266 Sep 25 16:57 demo.bmp

... but the image is corrupt when loaded into IrfanView. (My bad, should have actually checked yesterday!)

bitbank2 commented 1 month ago

Are you running the latest code from this repo or the version in the Arduino library manager? I don't see how this can occur. Do you have another OS where you can test this? WSL inside of Windows doesn't give me confidence that it's some other issue outside of my code.

robtinkers commented 1 month ago

I'm running the 1.6.1 release, specifically https://github.com/bitbank2/JPEGDEC/archive/refs/tags/1.6.1.tar.gz.

I just created a fresh install of "Ubuntu Server (minimized)" inside VirtualBox, and added the build-essential package:

rob@vmdevbox:~/JPEGDEC-1.6.1/linux$ sudo apt update && sudo apt upgrade
--- DELETED ---
The following upgrades have been deferred due to phasing:
  python3-distupgrade ubuntu-release-upgrader-core
0 upgraded, 0 newly installed, 0 to remove and 2 not upgraded.
rob@vmdevbox:~/JPEGDEC-1.6.1/linux$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.1 LTS
Release:        24.04
Codename:       noble
rob@vmdevbox:~/JPEGDEC-1.6.1/linux$ make
cc -c -Wall -O2 -I../src -D__LINUX__ main.c
In file included from main.c:11:
../src/jpeg.inl: In function ‘JPEGIDCT’:
../src/jpeg.inl:2264:9: warning: unused variable ‘iCol’ [-Wunused-variable]
 2264 |     int iCol;
      |         ^~~~
../src/jpeg.inl:2263:19: warning: unused variable ‘ucColMask’ [-Wunused-variable]
 2263 |     unsigned char ucColMask;
      |                   ^~~~~~~~~
../src/jpeg.inl: At top level:
../src/jpeg.inl:755:16: warning: ‘readFLASH’ defined but not used [-Wunused-function]
  755 | static int32_t readFLASH(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen)
      |                ^~~~~~~~~
../src/jpeg.inl: In function ‘JPEGFilter’:
../src/jpeg.inl:1421:25: warning: ‘xmmIn’ is used uninitialized [-Wuninitialized]
 1421 |         __m128i xmmFF = _mm_cmpeq_epi8(xmmIn, xmmIn);
      |                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
../src/jpeg.inl:1420:17: note: ‘xmmIn’ was declared here
 1420 |         __m128i xmmIn, xmmOut;
      |                 ^~~~~
cc main.o -lm -lpthread -o jpegdec
rob@vmdevbox:~/JPEGDEC-1.6.1/linux$ ./jpegdec
JPEG decoder demo
options:
Run without parameters to test in-memory decoding
Pass a single filename to test decoding performance
Pass 2 filenames to convert a JPEG file to a BMP file
e.g. ./jpegdec <infile.jpg> <outfile.bmp>

Segmentation fault (core dumped)

(Note that I haven't verified that the seg fault is for the same reason as under WSL.)

Current JPEGDEC-master also seg faults.

bitbank2 commented 1 month ago

Can you clone the current code (not the current released version)?

robtinkers commented 1 month ago

Also seg faults, I'm afraid.

bitbank2 commented 1 month ago

I added code to make the MCU buffer 16-byte aligned for S3 SIMD (and others). There is plenty of space for 64 x 6 values + 8 shorts to allow for alignment. It really makes no sense that this is segfaulting unless the MCU pointer is never set or the pointer is getting corrupted after the first pass.

bitbank2 commented 1 month ago

I'm able to recreate this on my Linux machine; I'll figure it out.

bitbank2 commented 1 month ago

ok, fixed it :) It was a wrong offset in the SSE code for 4:2:0 RGB565 output. Please clone and retest.

robtinkers commented 1 month ago

Great! I can confirm that jpegdec doesn't seg fault under WSL or Linux for me.

I'm sorry to report, though, that the decoded bitmaps (./jpegdec ../demo.jpg demo.bmp etc.) are still corrupt on both platforms.

bitbank2 commented 1 month ago

Corrupt doesn't mean anything by itself. Can you give me an example? Also try disabling the SIMD by setting "NO_SIMD".

robtinkers commented 1 month ago

Away from my PC for a bit. Are you unable to reproduce?

bitbank2 commented 1 month ago

I normally don't work on my x64 laptop; that's why this error exists. Give me an example of 'corruption'.

robtinkers commented 1 month ago

(Had a weekend away.)

Thank you! All seems good now.