pd-rs / crankstart

A barely functional, wildly incomplete and basically undocumented Rust crate whose aim is to let you write Games for the Playdate handheld gaming system in Rust.
MIT License
235 stars 24 forks source link

'Life' example segfaults simulator #26

Closed tjkirch closed 1 year ago

tjkirch commented 1 year ago

When I try to run the life example in the simulator, I get this:

> crank build --release --example life --run
    Finished release [optimized] target(s) in 0.02s
15:18:06: SDK: /home/tjk/code/playdate/PlaydateSDK-1.13.1-beta2-Linux-x86
15:18:06: Release: 1.13.1-beta.2
15:18:06: CMD: /home/tjk/code/playdate/crankstart/target/Life.pdx
15:18:06: Loading: /home/tjk/code/playdate/crankstart/target/Life.pdx/
Loading C API game: /home/tjk/code/playdate/crankstart/target/Life.pdx/pdex.so
15:18:06: Loading: OK
Error: open failed with error ExitStatus(unix_wait_status(11))

The same segfault happens with the 1.13.0 and 1.13.1-beta2 SDKs. This is on Linux with crankstart 8178447895d62d12b170fdbc0250ea090dddf592.

gdb shows Thread 1 "PlaydateSimulat" received signal SIGSEGV, Segmentation fault. with the following backtrace:

(gdb) bt
#0  0x00007fffec2b0c82 in life::game_setup::update () from target/Life.pdx/pdex.so
#1  0x00005555559bafd9 in pd_update ()
#2  0x0000555555941057 in sim_update () at ../source/simulator.c:676
#3  0x00005555558d7941 in MainFrame::UpdateSim (this=0x555556cd79e0) at ../source/mainframe.cpp:3259
#4  0x00005555558d7c0d in MainFrame::RenderFrame (this=0x555556cd79e0) at ../source/mainframe.cpp:3169
#5  0x0000555555e416a1 in wxEvtHandler::ProcessEventIfMatchesId(wxEventTableEntryBase const&, wxEvtHandler*, wxEvent&) ()
#6  0x0000555555e41b3e in wxEvtHandler::SearchDynamicEventTable(wxEvent&) ()
#7  0x0000555555e41f34 in wxEvtHandler::TryHereOnly(wxEvent&) ()
#8  0x0000555555e41fdf in wxEvtHandler::ProcessEventLocally(wxEvent&) ()
#9  0x0000555555e420e1 in wxEvtHandler::ProcessEvent(wxEvent&) ()
#10 0x0000555555e43198 in wxEvtHandler::ProcessPendingEvents() ()
#11 0x0000555555d24c37 in wxAppConsoleBase::ProcessPendingEvents() ()
#12 0x0000555555bbf87d in wxApp::DoIdle() ()
#13 0x0000555555bbf987 in wxapp_idle_callback ()
#14 0x00007ffff7167a2f in g_main_dispatch (context=0x555556c62850) at ../glib/gmain.c:3444
#15 g_main_context_dispatch (context=0x555556c62850) at ../glib/gmain.c:4162
#16 0x00007ffff7167dd8 in g_main_context_iterate (context=0x555556c62850, block=block@entry=1, dispatch=dispatch@entry=1, self=<optimized out>) at ../glib/gmain.c:4238
#17 0x00007ffff71680c3 in g_main_loop_run (loop=0x555556f0fc40) at ../glib/gmain.c:4438
#18 0x00007ffff77fa31d in gtk_main () from /usr/lib/libgtk-3.so.0
#19 0x0000555555bdcc45 in wxGUIEventLoop::DoRun() ()
#20 0x0000555555d5e111 in wxEventLoopBase::Run() ()
#21 0x0000555555d27033 in wxAppConsoleBase::OnRun() ()
#22 0x0000555555db66b7 in wxEntry(int&, wchar_t**) ()
#23 0x0000555555824fde in main (argc=<optimized out>, argv=0x7fffffffe678) at ../source/main.cpp:183

I'm not sure yet how to efficiently debug Playdate game crashes. I played around by adding panics at various places until I saw rust_begin_unwind in the backtrace, but it seems that no matter where I intentionally panic, it always takes the place of the segfault.

However, by luck, I managed to find a couple of workarounds, and I'm hoping these will point to a culprit. First, if I clear graphics before calling randomize, everything works fine:

         if !self.started {
+            graphics.clear(LCDColor::Solid(LCDSolidColor::kColorWhite))?;
             randomize(&graphics, &mut self.rng)?;
             self.started = true;
         }

It also works fine if I draw something to the screen before randomization, which doesn't seem anything like a real solution, but may be interesting data:

     fn update(&mut self, _playdate: &mut Playdate) -> Result<(), Error> {
         let graphics = Graphics::get();
+        graphics.draw_text("let's panic", point2(50, 50))?;
         if !self.started {

So, I suspect that something is reading from or writing to uninitialized state in some way..? I'm not experienced in this area.

If I build with --device it works fine on my 1.12.3 Playdate, oddly enough. [edit: also works on device after updating to 1.13.1.]

lilyinstarlight commented 1 year ago

So it actually works if I set the bindings generation script to generate a linux-specific binding and then I use that instead of the playdate binding file (which it defaults to when not on macOS)

I wonder if it's related to that or if that just hides some undefined behavior somewhere

Also this example definitely used to work in the simulator on Linux a while ago

rtsuk commented 1 year ago

It's surprising that it worked at all with the arm32 bindings on x64. This is definitely the right way to approach it.