Search code examples
linuxbashmemory-leakssegmentation-faultcoredump

Segmentation fault (core dumped) over a 100 round loop bash script


I know there are a lot of questions on that famous "Segmentation fault core dump", but for what I can see, C/C++ is involved in most of them. But my issue is specific to a bash script, so I am trying my luck here.

I am running a simulator script (400+ lines so I cannot show you the code, can I ?) which sets and calculates a fight between two characters using dices and a profile (like you would do in a Dungeons and Dragons tabletop RPG). I have a reinitialisation function to reset the fight from scratch so that it can start a new one when there is a winner. At the end of the X rounds, some values from it are displayed (% hit, %block %hp per round, etc).

If I set 1, 50 or 100 rounds (100 fights), it's okay, it runs perfectly. But over 130~ fights, all of sudden, it displays without any lag or other complications the "Segmentation fault (core dumped)" message.

I have a general idea of what it is, but I cannot explain why it happens, I cannot resolve it, and I do not know what to do, or what to look for.

Some notes I can say after browsing a lot of topics :

There is no import, no based command system, no sed, awk, no array, no "complicated" command. I am just playing with variables (integer). No string. The most "complexe" command (to get a random number) is probably

(echo $((1 + RANDOM % 20))) 

All my conditions are like this

if [[ "$Skill_Block2" == "Yes" ]];
then

With double bracket and variable between double quote (forgetting double quoted variable inside conditions could lead to problem, I heard).

There is no && or || or -a or -o (I also read that using direct "if" statement would be better)

The whole script is built around functions (easier to modify / implement). A function to calculate the damage is called by a function which check if a character can dodge the successful attack, which has been landed by another function allowing the success or the failure of that attack. Etc. I don't know if it is a good way of developping, but it "worked" so far.

I have accents and French characters, but they seem to be well managed by my OS version (Ubuntu).

I echo pretty much every single resolution so I can track mistakes. Perhaps displaying so much text is eating my virtual memory ? But I would never expect that on a Linux, to be honest.

I don't think I have infinite loops since I can run it 50++ times without any problem in the exact same order.

To display the stats, I am using a dirty way (I think) :

touch statistique.txt
    echo "#|Player 1|Player 2" > statistique.txt
    echo "ATT OK|$Number_Touch_OK1|$Number_Touch_OK1" >> statistique.txt
    echo "ATT Failed|$Number_Hit_Failed1|$Number_Hit_Failed2" >> statistique.txt
    echo "DEF Tried |$Number_Dodge_Tried1|$Number_Dodge_Tried2" >> statistique.txt
    [...]
    echo "Victory Number|$Victory1|$Victory2" >> statistique.txt
    echo " "
    column statistique.txt -t -s "|"

I tought about EOF, but I was not sure the variables would be interpreted. But at least I have a nicely formated text.

My Ubuntu is run on my Windows. Might be the problem ?


So here I am. I feel confused, and I am not very enthusiastic about posting this message as a wall of text without any code because it's too long (but if someone is brave enough, I can share the code, no problem). I have seen too few message about memory leak on bash, so... I cannot imagine a Linux OS running out of memory If you have any idea, advice, software (I tried Valgrind, but again, I am not sure it works with a bash script), please let me know.

EDIT : here's the file (solveur.sh) : https://github.com/IlliciteS/script


Solution

  • As pointed out in the comments, your program recurses endlessly. As each level of recursion consumes more memory, bash runs into the segmentation fault. Here's a minimal script to reproduce the problem (wrapped inside bash -c so that your interactive shell doesn't crash):

    $ bash -c 'f() { f; }; f'
    Segmentation fault (core dumped)
    

    You can prevent the segfault by limiting the maximum recursion level using bash's FUNCNEST variable. However, this will only abort a bit more gracefully. To solve the actual problem you have to rewrite your script.

    To identify the problem, You can look at the call graph of your script:

    call graph, generated using <code>{ fNames=$(grep -Po '^\w+(?=\(\))' solveur.sh); echo "digraph callgraph {"; tr \\n \\r < solveur.sh | grep -Po '\r\K\w+\(\).*?\r\}' | while read -r f; do; f=${f//$'\r'/$'\n'}; fName=$(echo "$f" | grep -Pom1 '\w+'); echo "$fName"; echo "$f" | tail -n+2 | grep -Fwof <(echo "$fNames") | sed "s/^/$fName -> /"; done; echo "}"; } > callgraph.dot;</code> and may not be 100% accurate

    Every cycle inside this graph could max out the call stack. A bit of recursion is ok. But for your task, loops seem to be more natural anyway. I would start by using a loop to start new fights. Delete Relance_Match() and change your main "method" to

    for ((i=0; i<Nombre_Match_Total; i++)); do
      Who_start_fight
      echo "..."
      Reinitialisation_Carac_New_Match
    done
    echo "..."
    Statistique
    

    Further Improvments Unrelated To Recursion

    You can drastically simplify your script with arrays. Right now, each player has its own set of variables and code fragments, e.g

    # for player one
    Victoire1=0
    Touch1() {
      # lots of code using <insertThingHere>1, e.g.
      ((Victoire1++))
    }
    
    # for player two
    Victoire2=0
    Touch2() {
      # the same code as in Touch1, but with <insertThingHere>2 instead, e.g.
      ((Victoire2++))
    }
    

    With arrays this could be simplified to

    Victoire=(0 0)
    Touch() {
      player=$1
      # lots of code using <insertThingHere>[player], e.g.
      ((Victoire[player]++));
    }