CoppeliaRobotics / coppeliaSimLib

CoppeliaSim core library
Other
104 stars 40 forks source link

Dynamic linker issues when coppeliaSim.sh is invoked from a relative path #21

Closed EliaCereda closed 1 year ago

EliaCereda commented 1 year ago

We are seeing that invoking coppeliaSim.sh from a relative path can cause issues with the dynamic linker. A relative path to the CoppeliaSim root dir is added to LD_LIBRARY_PATH, which results in the dynamic linker not being able to find some libraries.

You can find attached two logs in which coppeliaSim.sh is invoked either from a relative or an absolute path. I added set -x at the beginning of the script so you can see printed the values of every variable.

The error is at line 31 of relative_path.log. It is happening when one of our plugins tries to load one of its dependent dynamic library, which is also in the CoppeliaSim root dir. We will investigate if anything in the build process for our plugin is also responsible for this error, but at the same time I believe there is an easy way to make coppeliaSim.sh more robust.

With the edit below, $dirname is always resolved to an absolute path before being added to LD_LIBRARY_PATH, avoiding this kind of issues completely. It is a more general version of the $dirname = "." check, which works for all paths.

coppeliaSim.sh:

   dirname=`dirname "$thisscript"`
+  dirname=`realpath "$dirname"`
-  if [ $dirname = "." ]; then
-          dirname="$PWD"
-  fi

   appname="`basename "$thisscript" | sed 's,\.sh$,,'`"

relative_path.log absolute_path.log

The problem appears to be platform-agnostic, the logs above are from an official pre-built installation of CoppeliaSim Edu on Linux x86_64.

Cc: @Dario-Mantegazza @jeguzzi

Coppelia commented 1 year ago

Thanks again Elida. Yes, that is better! Since V4.3 we actually use a script to adjust the rpath of every binary in the installation package. So the coppeliaSim.sh script is not really needed anymore.

Following is the script we use:

import os
import re
import sys

l=len(sys.argv)
dirName = sys.argv[1]

def getFilesAndDirs(d):
    for (dirpath, dirnames, filenames) in os.walk(d,followlinks=False):
        retFiles = []
        retDirs = []
        for filename in filenames:
            retFiles.append(os.path.join(dirpath,filename))
        for dd in dirnames:
            retDirs.append(os.path.join(dirpath,dd))
        return retFiles,retDirs

def handleFile(f,l):
    fn,extent = os.path.splitext(f.lower())
    if ((f.endswith(".so") == True) or (re.search("\.so\.([^/])+",f) != None) or ((extent == '') and (l == 0)) ) and (re.search("libtbb",f) == None):
        toAdd = ""
        for i in range(l+1):
            x = "$ORIGIN"
            for j in range(i):
                x = x + "/.."
            if i > 0:
                toAdd = toAdd + ":"
            toAdd = toAdd + x

        stream = os.popen("readelf -d "+f+" |head -20")    
        txt = stream.read()
        x = re.search("\(RUNPATH\) *Library runpath: \[(([^\]])*)\]",txt)
        toKeep = ''
        if x and x.group(1):
            x = x.group(1)
            if x[0] == '/':
                if re.search("/home",x) == None:
                    toKeep=x # absolute and apparently generic path. Keep it!
                    if toKeep[len(toKeep)-1] == ':':
                        toKeep = toKeep[:-1]
        rpath = toKeep
        if len(rpath) != 0:
            rpath = rpath + ':' + toAdd
        else:
            rpath = toAdd
        stream = os.popen("patchelf --set-rpath '"+rpath+"' "+f,'w')

def handleFiles(d,l):
    files,dirs=getFilesAndDirs(d)
    for f in files:
        handleFile(f,l)
    for f in dirs:
        handleFiles(f,l+1)

handleFiles(dirName,0)