pygame-community / pygame-ce

🐍🎮 pygame - Community Edition is a FOSS Python library for multimedia applications (like games). Built on top of the excellent SDL library.
https://pyga.me
939 stars 156 forks source link

Fatal Python error: Segmentation fault caused by Pygame #2694

Open Yyote opened 10 months ago

Yyote commented 10 months ago

Environment:

Current behavior:

This issue has appeared in both original pygame and in pygame-ce.

I have a game in which some cars navigate the environment with obstacles using sensor data and they collide quite frequently with each other and with obstacles. The problem is that sometimes during collisions the program crashes. It throws Fatal Python error: Segmentation fault but I'm 99% sure, that it's pygame. I use mask.overlap() function to detect collisions and when I remove parts that use this function, the program works perfectly fine. What's interesting but very confusing is that the trace points to the blit() function. Maybe I should also mention that I rotate the sprites using rotozoom() and create masks from rotated images.

Expected behavior:

Pixel-perfect sprite collisions using mask.overlap() without getting segmentation faults.

Steps to reproduce:

Create a number of moving sprites and check collisions between them using the mask.overlap() function.

Unfortunately, I cannot easily provide an adequate path to reproduce the issue as I am not sure what really causes it (except that it's part of pygame). If you are really interested, I can provide the repo with the code of my program, but it also uses ROS2 and some of its additional packages so you will be required to download them. Then the steps to reproduce are:

  1. Launch the program
  2. Use arrows to move and collide with objects
  3. Try to collid with objects until it crashes

Stack trace/error output/other error logs

Fatal Python error: Segmentation fault

Current thread 0x00007f504d0a7740 (most recent call first):
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/object_tools.py", line 71 in draw
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/robots.py", line 103 in call_sensors
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/robots.py", line 108 in draw
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/gametools.py", line 141 in draw_every_sprite_in_list
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/main.py", line 94 in main
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/gr_kinematic_sim/sim", line 11 in <module>
Fatal Python error: (pygame parachute) Segmentation Fault
Python runtime state: initialized

Current thread 0x00007f504d0a7740 (most recent call first):
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/object_tools.py", line 71 in draw
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/robots.py", line 103 in call_sensors
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/robots.py", line 108 in draw
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/custom_utils/gametools.py", line 141 in draw_every_sprite_in_list
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/python3.8/site-packages/gr_kinematic_sim/main.py", line 94 in main
  File "/home/leev/workspaces/gr-kinematic-sim/install/gr_kinematic_sim/lib/gr_kinematic_sim/sim", line 11 in <module>
free(): invalid pointer

The mentioned draw() function:

    def draw(self):
        rect_to_draw = copy(self.rect)
        rect_to_draw.x += self.curr_offset_x
        rect_to_draw.y += self.curr_offset_y
        rotated_image = pygame.transform.rotozoom(self._original_image, self._current_rotation, 1)
        self.screen.blit(rotated_image, rect_to_draw)
Starbuck5 commented 9 months ago

Thanks for your report.

Without a reproducible script it's very hard to make any progress against a segfault. You mentioned a repo?

Your traceback has thread identifiers in it? Are you calling into pygame-ce from multiple threads? That's a dicey prospect, and could explain a segfault.

Yyote commented 7 months ago

I will try to provide something but as for now I cannot give access to the repo. But I found out, that the segfault is caused by the linear_velocity variable I use that for some reason became nan. After adding nan to rect coordinates and trying to draw a sprite in that position it resulted in segfault. I don't know, why it happend, but checking it with math.isnan() solved the issue for me. So most probably it is my fault and not pygame's

oddbookworm commented 7 months ago

I’m going to reopen this for now. Regardless of the cause, if it’s actually a segfault happening in pygame-ce, it should be fixed on our end. No input should be so broken that we segfault

MrDixioner commented 5 months ago

I'm getting the same error Fatal Python error: pygame_parachute: (pygame parachute) Segmentation Fault Python runtime state: initialized Current thread 0x00000ec8 (most recent call first):

In order not to create a new topic, I will write here. Windows 10 x64 Python 3.12.3 PyGame-CE 2.5.0.dev2 IDE: PyCharm 2024.1.1

I wrote the source code of the program manually according to the video tutorial: (https://youtu.be/Kw2xdbyxsGw?si=n0rfXrl6ZbfCpf1S) You can take all the resources for checking the error using the link from the author of the video (take only the img folder): (https://github.com/russs123/LevelEditor)

The error first occurs on line 32 (screen.blit(pine1_img, etc.)), then on line 37 (draw_bg()).

The error does not appear immediately, but after I move the image in the game to the right by pressing the right key (you need to hold it until the error appears).

The strangest thing is that the error only occurs when using the file pine1.png. I tried changing it to other files, tried duplicating them in a loop and the program works fine, but as soon as I add the file pine1.png the program immediately crashes.

Source:

import pygame as pg
pg.init()

SCREEN_WIDTH=800
SCREEN_HEIGHT=640
LOWER_MARGIN=100
SIDE_MARGIN=300

screen=pg.display.set_mode((SCREEN_WIDTH+SIDE_MARGIN, SCREEN_HEIGHT+LOWER_MARGIN))
pg.display.set_caption("Level Editor")

scroll_left:bool=False
scroll_right:bool=False
scroll=0
scroll_speed=1

pine1_img=pg.image.load("img/Background/pine1.png").convert_alpha()
pine2_img=pg.image.load("img/Background/pine2.png").convert_alpha()
mountain_img=pg.image.load("img/Background/mountain.png").convert_alpha()
sky_img=pg.image.load("img/Background/sky_cloud.png").convert_alpha()

GREEN=(144,201,120)
WHITE=(255,255,255)
RED=(200,25,25)

def draw_bg():
    screen.fill(GREEN)
    width=sky_img.get_width()
    for x in range(4):
        screen.blit(sky_img, ((x*width)-scroll,0))
        screen.blit(mountain_img, ((x*width)-scroll, SCREEN_HEIGHT-mountain_img.get_height()-300))
        screen.blit(pine1_img, ((x*width)-scroll, SCREEN_HEIGHT-pine1_img.get_height()-150))
        screen.blit(pine2_img, ((x*width)-scroll, SCREEN_HEIGHT-pine2_img.get_height()))

run=True
while run:
    draw_bg()

    if scroll_left and scroll>0:
        scroll-=5
    if scroll_right:
        scroll+=5

    for event in pg.event.get():
        if event.type==pg.QUIT:
            run=False

        if event.type==pg.KEYDOWN:
            if event.key==pg.K_LEFT:
                scroll_left=True
            if event.key==pg.K_RIGHT:
                scroll_right=True

        if event.type==pg.KEYUP:
            if event.key==pg.K_LEFT:
                scroll_left=False
            if event.key==pg.K_RIGHT:
                scroll_right=False

    pg.display.update()

pg.quit()
ankith26 commented 5 months ago

Hello, thanks for the bug report!

I cannot reproduce your issue on my standard Ubuntu 24.04 install. Perhaps it's a windows specific thing.

Your issue could be unrelated to this issue, but I feel it could be related to #2288 which should be fixed in v2.5.0.dev2 to the best of my knowledge.

Just to be sure, can you run pygame.print_debug_info() and paste the output here?

MyreMylar commented 5 months ago

I couldn't reproduce this locally on windows. I tried with current main and 2.5.0.dev2 from PyPI.

MyreMylar commented 5 months ago

This segfaults for me pretty quick:

import pygame
import pygame as pg

pg.init()

SCREEN_WIDTH = 800
SCREEN_HEIGHT = 640
LOWER_MARGIN = 100
SIDE_MARGIN = 300

screen = pg.display.set_mode((SCREEN_WIDTH + SIDE_MARGIN, SCREEN_HEIGHT + LOWER_MARGIN))
pg.display.set_caption("Level Editor")

scroll_left: bool = False
scroll_right: bool = False
scroll = 0
scroll_speed = 1

pine1_img = pg.image.load("img/Background/pine1.png").convert_alpha()
pine2_img = pg.image.load("img/Background/pine2.png").convert_alpha()
mountain_img = pg.image.load("img/Background/mountain.png").convert_alpha()
sky_img = pg.image.load("img/Background/sky_cloud.png").convert_alpha()

GREEN = (144, 201, 120)
WHITE = (255, 255, 255)
RED = (200, 25, 25)

pos_rect = pygame.FRect(float("nan"), 0.0, 100.0, 100.0)

def draw_bg():
    screen.fill(GREEN)
    width = sky_img.get_width()
    for x in range(4):
        screen.blit(sky_img, ((x * width) - scroll, 0))
        pos_rect.y += scroll
        screen.blit(
            mountain_img,
            pos_rect,
        )
        screen.blit(
            pine1_img,
            pos_rect,
        )
        screen.blit(pine2_img, pos_rect)

run = True
while run:
    draw_bg()

    if scroll_left and scroll > 0:
        scroll -= 5
    if scroll_right:
        scroll += 5

    for event in pg.event.get():
        if event.type == pg.QUIT:
            run = False

        if event.type == pg.KEYDOWN:
            if event.key == pg.K_LEFT:
                scroll_left = True
            if event.key == pg.K_RIGHT:
                scroll_right = True

        if event.type == pg.KEYUP:
            if event.key == pg.K_LEFT:
                scroll_left = False
            if event.key == pg.K_RIGHT:
                scroll_right = False

    pg.display.update()

pg.quit()
damusss commented 5 months ago

The code @MyreMylar provided segfaults on my windows 10 machine quickly aswell

To be specific, without pos_rect.y += scroll it does not segfault

MyreMylar commented 5 months ago

simplified:

import pygame

pygame.init()

screen = pygame.display.set_mode((800, 600))

scroll_down: bool = False
scroll = 0

test_surf = pygame.Surface((1057, 398), flags=pygame.SRCALPHA)
test_surf.fill((255, 0, 0))
pos_rect = pygame.FRect(float("nan"), 201.0, 100.0, 100.0)

run = True
while run:
    screen.fill((144, 201, 120))

    if scroll_down:
        pos_rect.y += 1.0

    screen.blit(test_surf, pos_rect)

    print(pos_rect)

    for event in pygame.event.get():

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_DOWN:
                scroll_down = True

        if event.type == pygame.KEYUP:
            if event.key == pygame.K_DOWN:
                scroll_down = False

    pygame.display.update()
MyreMylar commented 5 months ago

If the surface is any smaller it won't segfault. It also always segfaults at 202 y-position. Requires the NaN in the position there.

MyreMylar commented 5 months ago

It's got to be something to do with clipping as it segfaults the moment the surface overlaps the bottom of the screen.

MrDixioner commented 5 months ago

Just to be sure, can you run pygame.print_debug_info() and paste the output here?


Platform:       Windows-10-10.0.19045-SP0
System:         Windows
System Version:     10.0.19045
Processor:      AMD64 Family 16 Model 2 Stepping 3, AuthenticAMD
Architecture:       Bits: 64bit Linkage: WindowsPE

Python: CPython 3.12.3 (tags/v3.12.3:f6650f9, Apr 9 2024, 14:05:25) [MSC v.1938 64 bit (AMD64)] pygame version: 2.5.0.dev2 SDL versions: Linked: 2.30.2 Compiled: 2.30.2 SDL Mixer versions: Linked: 2.8.0 Compiled: 2.8.0 SDL Font versions: Linked: 2.22.0 Compiled: 2.22.0 SDL Image versions: Linked: 2.8.2 Compiled: 2.8.2 Freetype versions: Linked: 2.11.1 Compiled: 2.11.1

Display Driver: windows Mixer Driver: wasapi


I don’t understand why the error appears with only one file (**pine1.png**)? Why are the other images okay?

I would like to add that the author of the video does not have this error. It uses PyGame 2.0.1 and Python 3.9.0, and also works on Windows.
Starbuck5 commented 5 months ago

@MrDixioner looking at your processor AMD64 Family 16 Model 2 Stepping 3, AuthenticAMD I noticed that it doesn't support AVX2 instructions and so will go down a different code path as most testers. I built a custom version of pygame-ce without AVX2 support and was able to reproduce your segfault locally.

The theory right now is that Yyote's segfault is about weird float values and/or huge numbers, and that yours has a different cause entirely.

Starbuck5 commented 5 months ago

Simplest reproducer yet: *worth noting that pine1.png is 1376 pixels across

import pygame
pygame.init()

screen=pygame.display.set_mode((200, 200))

pine1_img=pygame.image.load("img/Background/pine1.png").convert_alpha()

# -1372 is fine, -1373, -1374, -1375 segfaults, -1376 is fine
# experiments also revealed a region 197-199 with the same behavior.

# SSE2 code path!

screen.blit(pine1_img, (-1373, 0))

print("Done?")
Starbuck5 commented 5 months ago

@MrDixioner I've got it all figured out now in https://github.com/pygame-community/pygame-ce/pull/2896

Thank you for the report!

For you to be able to work around this, try installing pygame-ce==2.4.1, the latest stable version. I haven't tested yet because I need to build from source to test because of my CPU, but I'm confident 2.4.1 doesn't have the problem you've run into.

I've also created a built version of my patched pygame-ce version for you to test: pygame_ce-2.5.0.dev3-cp312-cp312-win_amd64.zip

To install, download that file locally, change the file extension from zip to whl (thanks GitHub) and then pip install it directly. (i.e. pip install pygame_ce-2.5.0.dev3-cp312-cp312-win_amd64.whl from the folder it is in.

MrDixioner commented 5 months ago

I've also created a built version of my patched pygame-ce version for you to test: pygame_ce-2.5.0.dev3-cp312-cp312-win_amd64.zip

Yes, now everything works without errors! Thanks a lot! P.S. You're right, I have a pretty old processor :(