libsdl-org / sdl12-compat

An SDL-1.2 compatibility layer that uses SDL 2.0 behind the scenes.
Other
197 stars 40 forks source link

boswars: Segfault during gameplay, in audio thread #232

Closed smcv closed 1 year ago

smcv commented 2 years ago

Prerequisites:

To reproduce:

The game segfaults with:

Thread 1 (Thread 0x7ff4255cd640 (LWP 18959)):
#0  AllocateDataQueuePacket (queue=queue@entry=0x55ad53896ae0) at ./src/SDL_dataqueue.c:149
#1  0x00007ff43e06c119 in SDL_WriteToDataQueue (queue=0x55ad53896ae0, _data=<optimized out>, _len=<optimized out>) at ./src/SDL_dataqueue.c:197
#2  0x00007ff43ef083ce in AudioCallbackWrapper (userdata=0x55ad539e4090, stream=0x55ad539b6f60 "", len=16384) at /home/desktop/tmp/sdl12-compat/src/SDL12_compat.c:8919
#3  0x00007ff43e06df38 in SDL_RunAudio (devicep=devicep@entry=0x55ad53ac7040) at ./src/audio/SDL_audio.c:749
#4  0x00007ff43e0d7e65 in SDL_RunThread (thread=0x55ad53aa1c30) at ./src/thread/SDL_thread.c:303
#5  0x00007ff43e16fc49 in RunThread (data=<optimized out>) at ./src/thread/pthread/SDL_systhread.c:77
#6  0x00007ff43e88784a in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#7  0x00007ff43e90b0ac in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

At the time of the crash, all other threads are blocked waiting on locks, except for these two:

Thread 7 (Thread 0x7ff43877c640 (LWP 18948)):
#0  0x00007ff43e8fe2e6 in __ppoll (fds=0x55ad527c6970, nfds=2, timeout=<optimized out>, sigmask=sigmask@entry=0x0) at ../sysdeps/unix/sysv/linux/ppoll.c:42
#1  0x00007ff43e4d1029 in ppoll (__ss=0x0, __timeout=<optimized out>, __nfds=<optimized out>, __fds=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/poll2.h:64
#2  pa_mainloop_poll (m=m@entry=0x55ad527c9050) at ../src/pulse/mainloop.c:871
#3  0x00007ff43e4d1606 in pa_mainloop_iterate (m=m@entry=0x55ad527c9050, block=block@entry=1, retval=retval@entry=0x0) at ../src/pulse/mainloop.c:945
#4  0x00007ff43e4d16b0 in pa_mainloop_run (m=0x55ad527c9050, retval=0x0) at ../src/pulse/mainloop.c:963
#5  0x00007ff43e13b7ef in HotplugThread (data=data@entry=0x0) at ./src/audio/pulseaudio/SDL_pulseaudio.c:841
#6  0x00007ff43e0d7e65 in SDL_RunThread (thread=0x55ad527e8470) at ./src/thread/SDL_thread.c:303
#7  0x00007ff43e16fc49 in RunThread (data=<optimized out>) at ./src/thread/pthread/SDL_systhread.c:77
#8  0x00007ff43e88784a in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#9  0x00007ff43e90b0ac in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81

Thread 6 (Thread 0x7ff43e4ff940 (LWP 18945)):
#0  0x00007ff43e8d1305 in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7fff9a852730, rem=rem@entry=0x7fff9a852720) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:48
#1  0x00007ff43e8d5d83 in __GI___nanosleep (req=req@entry=0x7fff9a852730, rem=rem@entry=0x7fff9a852720) at ../sysdeps/unix/sysv/linux/nanosleep.c:25
#2  0x00007ff43e177071 in SDL_Delay_REAL (ms=<optimized out>) at ./src/timer/unix/SDL_systimer.c:219
#3  0x000055ad51fcc976 in WaitEventsOneFrame() () at engine/video/sdl.cpp:901
#4  0x000055ad51f50d48 in GameMainLoop() () at engine/game/mainloop.cpp:358
#5  0x000055ad51f8590d in StartMap(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool) (filename="maps/islandwar.map/presentation.smp", clean=<optimized out>) at engine/stratagus/stratagus.cpp:399
#6  0x000055ad51ff3f56 in tolua_stratagus_StartMap00(lua_State*) (tolua_S=0x55ad52735150) at engine/tolua/tolua.cpp:16706
#7  0x00007ff43f181119 in luaD_precall (L=L@entry=0x55ad52735150, func=func@entry=0x55ad53e76e10, nresults=nresults@entry=0) at ./src/ldo.c:320
#8  0x00007ff43f18cab7 in luaV_execute (L=L@entry=0x55ad52735150, nexeccalls=2, nexeccalls@entry=1) at ./src/lvm.c:591
#9  0x00007ff43f1817a5 in luaD_call (L=0x55ad52735150, func=0x55ad53e76d90, nResults=<optimized out>) at ./src/ldo.c:378
#10 0x00007ff43f180a9b in luaD_rawrunprotected (L=L@entry=0x55ad52735150, f=f@entry=0x7ff43f179da0 <f_call>, ud=ud@entry=0x7fff9a852d30) at ./src/ldo.c:116
#11 0x00007ff43f181950 in luaD_pcall (L=L@entry=0x55ad52735150, func=func@entry=0x7ff43f179da0 <f_call>, u=u@entry=0x7fff9a852d30, old_top=560, ef=<optimized out>) at ./src/ldo.c:464
#12 0x00007ff43f17cd78 in lua_pcall (L=0x55ad52735150, nargs=<optimized out>, nresults=nresults@entry=0, errfunc=<optimized out>) at ./src/lapi.c:821
#13 0x000055ad51f80698 in LuaCallback::run() (this=this@entry=0x55ad53eb0b98) at engine/stratagus/luacallback.cpp:101
#14 0x000055ad51fa44ed in LuaActionListener::action(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (this=0x55ad53eb0b90, eventId=<optimized out>) at engine/ui/widgets.cpp:198
#15 0x000055ad5200ef1d in gcn::Widget::generateAction(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (eventId="", this=0x55ad53eb6000) at engine/guichan/widget.cpp:596
#16 gcn::Widget::generateAction() (this=0x55ad53eb6000) at engine/guichan/widget.cpp:588
#17 0x000055ad5200e6a3 in gcn::Widget::_mouseInputMessage(gcn::MouseInput const&) (this=0x55ad53eb6000, mouseInput=<optimized out>) at engine/guichan/widget.cpp:478
#18 0x000055ad52013697 in gcn::Container::_mouseInputMessage(gcn::MouseInput const&) (this=0x55ad53ead690, mouseInput=...) at engine/guichan/widgets/container.cpp:368
#19 0x000055ad5200bf2a in gcn::Gui::logic() (this=0x55ad539bdbe0) at engine/guichan/gui.cpp:154
#20 0x000055ad51fccc59 in WaitEventsOneFrame() () at engine/video/sdl.cpp:967
#21 0x000055ad51fa43cf in MenuScreen::run(bool) (this=this@entry=0x55ad53ead690, loop=loop@entry=true) at engine/ui/widgets.cpp:1501
#22 0x000055ad51feae45 in tolua_stratagus_CMenuScreen_run00 (tolua_S=0x55ad52735150) at engine/tolua/tolua.cpp:11864
#23 tolua_stratagus_CMenuScreen_run00(lua_State*) (tolua_S=0x55ad52735150) at engine/tolua/tolua.cpp:11845
#24 0x00007ff43f181119 in luaD_precall (L=L@entry=0x55ad52735150, func=func@entry=0x55ad53e76d60, nresults=nresults@entry=0) at ./src/ldo.c:320
#25 0x00007ff43f18cab7 in luaV_execute (L=L@entry=0x55ad52735150, nexeccalls=nexeccalls@entry=1) at ./src/lvm.c:591
#26 0x00007ff43f1817a5 in luaD_call (L=0x55ad52735150, func=0x55ad53e76c10, nResults=<optimized out>) at ./src/ldo.c:378
#27 0x00007ff43f180a9b in luaD_rawrunprotected (L=L@entry=0x55ad52735150, f=f@entry=0x7ff43f179da0 <f_call>, ud=ud@entry=0x7fff9a8534a0) at ./src/ldo.c:116
#28 0x00007ff43f181950 in luaD_pcall (L=L@entry=0x55ad52735150, func=func@entry=0x7ff43f179da0 <f_call>, u=u@entry=0x7fff9a8534a0, old_top=176, ef=<optimized out>) at ./src/ldo.c:464
#29 0x00007ff43f17cd78 in lua_pcall (L=0x55ad52735150, nargs=<optimized out>, nresults=nresults@entry=0, errfunc=<optimized out>) at ./src/lapi.c:821
#30 0x000055ad51f80698 in LuaCallback::run() (this=this@entry=0x55ad53e77b98) at engine/stratagus/luacallback.cpp:101
#31 0x000055ad51fa44ed in LuaActionListener::action(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (this=0x55ad53e77b90, eventId=<optimized out>) at engine/ui/widgets.cpp:198
#32 0x000055ad5200ef1d in gcn::Widget::generateAction(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (eventId="", this=0x55ad527aeaa0) at engine/guichan/widget.cpp:596
#33 gcn::Widget::generateAction() (this=0x55ad527aeaa0) at engine/guichan/widget.cpp:588
#34 0x000055ad5200e6a3 in gcn::Widget::_mouseInputMessage(gcn::MouseInput const&) (this=0x55ad527aeaa0, mouseInput=<optimized out>) at engine/guichan/widget.cpp:478
#35 0x000055ad52013697 in gcn::Container::_mouseInputMessage(gcn::MouseInput const&) (this=0x55ad53eaadb0, mouseInput=...) at engine/guichan/widgets/container.cpp:368
#36 0x000055ad5200bf2a in gcn::Gui::logic() (this=0x55ad539bdbe0) at engine/guichan/gui.cpp:154
#37 0x000055ad51fccc59 in WaitEventsOneFrame() () at engine/video/sdl.cpp:967
#38 0x000055ad51fa43cf in MenuScreen::run(bool) (this=this@entry=0x55ad53eaadb0, loop=loop@entry=true) at engine/ui/widgets.cpp:1501
#39 0x000055ad51feae45 in tolua_stratagus_CMenuScreen_run00 (tolua_S=0x55ad52735150) at engine/tolua/tolua.cpp:11864
#40 tolua_stratagus_CMenuScreen_run00(lua_State*) (tolua_S=0x55ad52735150) at engine/tolua/tolua.cpp:11845
#41 0x00007ff43f181119 in luaD_precall (L=L@entry=0x55ad52735150, func=func@entry=0x55ad53e76be0, nresults=nresults@entry=1) at ./src/ldo.c:320
#42 0x00007ff43f18cab7 in luaV_execute (L=L@entry=0x55ad52735150, nexeccalls=2, nexeccalls@entry=1) at ./src/lvm.c:591
#43 0x00007ff43f1817a5 in luaD_call (L=0x55ad52735150, func=0x55ad527b1840, nResults=<optimized out>) at ./src/ldo.c:378
#44 0x00007ff43f180a9b in luaD_rawrunprotected (L=L@entry=0x55ad52735150, f=f@entry=0x7ff43f179da0 <f_call>, ud=ud@entry=0x7fff9a853c10) at ./src/ldo.c:116
#45 0x00007ff43f181950 in luaD_pcall (L=L@entry=0x55ad52735150, func=func@entry=0x7ff43f179da0 <f_call>, u=u@entry=0x7fff9a853c10, old_top=32, ef=<optimized out>) at ./src/ldo.c:464
#46 0x00007ff43f17cd78 in lua_pcall (L=0x55ad52735150, nargs=nargs@entry=0, nresults=nresults@entry=0, errfunc=errfunc@entry=1) at ./src/lapi.c:821
#47 0x000055ad51f81172 in LuaCall(int, int, bool) (narg=narg@entry=0, clear=clear@entry=1, exitOnError=exitOnError@entry=true) at engine/stratagus/script.cpp:195
#48 0x000055ad51f81341 in LuaLoadFile(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) (file="/usr/share/games/boswars/scripts/guichan.lua") at engine/stratagus/script.cpp:278
#49 0x000055ad51f85bee in MenuLoop() () at /usr/include/c++/9/ext/new_allocator.h:80
#50 0x000055ad51f30ca8 in main1 (argc=<optimized out>, argv=<optimized out>) at engine/stratagus/stratagus.cpp:559
#51 main(int, char**) (argc=<optimized out>, argv=<optimized out>) at engine/stratagus/stratagus.cpp:915
icculus commented 2 years ago

So I thought this was going to be a good test case for the SDL2 resampling bug, but it turned out to not be:

SDL_BuildAudioResamplerCVT in SDL2:

/* !!! FIXME: remove this if we can get the resampler to work in-place again. */
/* the buffer is big enough to hold the destination now, but
   we need it large enough to hold a separate scratch buffer. */
cvt->len_mult *= 2;

But boswars does this in ConvertToStereo32:

return acvt.len_mult * bytes;

And counts on that return value to fit into a static buffer they use to convert audio; since they know how much space this should take, they statically allocate just enough and then don't check to see that this return value is double what they expect.

This is a bug in boswars, to be clear, but it works with real SDL 1.2 because it resamples in-place and SDL2 does not. I'm not sure how to fix this yet, and have other things to focus on right now, so I'm just leaving some notes here.

(SDL 1.2 would overflow buffers without warning if not resampling between frequencies that aren't double (11025 to 22050 would work but 44100 to 48000 would not), and maybe we need to do a simple in-place resample in this case instead of handing it to SDL2.)