Nástroje používateľa

Nástoje správy stránok


blog:odborny:2025-01-09-tweaking_apps_executables_and_dynamic_libraries_with_install_name_tool_and_otool

Tweaking apps, executables and dynamic libraries with install_name_tool and otool

Sometimes you want to investigate dynamic libraries (*.dylib) the app is using, relink them or remove unused ones from the *.app package. install_name_tool and otool are here to help you with that!

otool: List libraries loaded/needed by executable or dynamic library

Each executable might call dynamic libraries (*.dylibs) which it requires in order to run. And in turn, each dynamic library might also call other dynamic libraries which it requires. To see these libraries, use:

otool -L <executable_or_dylib>

For example, running otool L ./ScanTailor will print something like this:

$ otool -L ./ScanTailor
./ScanTailor:
  @executable_path/../Frameworks/libtiff.6.dylib (compatibility version 7.0.0, current version 7.1.0)
  @executable_path/../Frameworks/libpng16.16.dylib (compatibility version 56.0.0, current version 56.0.0)
  /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
  @executable_path/../Frameworks/libjpeg.9.dylib (compatibility version 8.0.0, current version 8.2.2)
  @rpath/QtNetwork.framework/Versions/5/QtNetwork (compatibility version 5.12.0, current version 5.12.12)
  @rpath/QtOpenGL.framework/Versions/5/QtOpenGL (compatibility version 5.12.0, current version 5.12.12)
  @rpath/QtSvg.framework/Versions/5/QtSvg (compatibility version 5.12.0, current version 5.12.12)
  @rpath/QtWidgets.framework/Versions/5/QtWidgets (compatibility version 5.12.0, current version 5.12.12)
  @rpath/QtGui.framework/Versions/5/QtGui (compatibility version 5.12.0, current version 5.12.12)
  @rpath/QtXml.framework/Versions/5/QtXml (compatibility version 5.12.0, current version 5.12.12)
  @rpath/QtCore.framework/Versions/5/QtCore (compatibility version 5.12.0, current version 5.12.12)
  /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 800.7.0)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.0.0)

This will list the required libraries and also show you where the executable is looking for each one of them. If it's not there, the app will simply fail to open. Running the executable from Terminal will reveal the reason:

$ ./ScanTailor
dyld: Library not loaded: @rpath/QtNetwork.framework/Versions/5/QtNetwork
  Referenced from: ~/Applications/Scantailor-FurloSK.app/Contents/MacOS/ScanTailor
  Reason: image not found
Abort trap: 6

There are two ways to fix that:

  1. Supply the library itself and move it to the destination requested by the executable.
  2. If you have the library, but it resides in other location, you might want to change the path at which the executable itself is looking for the library.

The second case is actually the most common, since you usually have system libraries residing at /usr/lib/*, which you cannot change nor update, and your own manuallyinstalled libraries (customized or updated) at /usr/local/lib/*. The following sections will cover exactly this procedure.

otool: Investigate dynamic load commands embedded in executable or dynamic library

The calls to dynamic libraries are made by dynamic load commands embedded in executables. To see all of these commands in an executable (lib or bin), type:

otool -l <executable_or_dylib>

This will print a list of exact commands the executable contains, and not just the list of libraries as with otool L with which we dealt in previous section.

This will be important since, as we have already seen, some of the paths to libraries contain special keywords like @executable_path, @rpath or @loader_path. For this reason, just listing the loaded dynamic libraries with their paths is usually not enough and closer examination of the dynamic load commands is needed.

All the special keywords are explained in more detail in this article. I will cover them here just in the extent needed to understand dynamic load commands. These commands might be:

LC_LOAD_DYLIB with absolute path:

Load command 23
          cmd LC_LOAD_DYLIB
      cmdsize 48
         name /usr/lib/libc++.1.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 800.7.0
compatibility version 1.0.0

This is just a load command with plain absolute path to library, nothing fancy here.

LC_LOAD_DYLIB with @executable_path:

Load command 15
          cmd LC_LOAD_DYLIB
      cmdsize 72
         name @executable_path/../Frameworks/libjpeg.9.dylib (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 8.2.2
compatibility version 8.0.0

The paths in these commands start with special keyword @executable_path. When the app or executable is run, this keyword is resolved into the path to the executable calling the dynamic library.

LC_LOAD_DYLIB with @rpath:

Load command 22
          cmd LC_LOAD_DYLIB
      cmdsize 72
         name @rpath/QtCore.framework/Versions/5/QtCore (offset 24)
   time stamp 2 Thu Jan  1 01:00:02 1970
      current version 5.12.12
compatibility version 5.12.0

This is a crucial kind of load command. When the app or executable is run, the @rpath keyword is resolved into the path which is specified in the dynamic library itself by another dynamic load command: LC_RPATH.

LC_RPATH specifying @rpath:

Load command 25
          cmd LC_RPATH
      cmdsize 48
         path @executable_path/../Frameworks (offset 12)

This command simply allows you to store in the dynamic library itself a path, into which every @rpath keyword in all of the other LC_LOAD_DYLIB commands will be resolved. This is important for two reasons:

  1. First, it allows for easy manipulation of the executable dependencies, since the path to all the dynamic libraries which use @rpath keyword can be changed at once by simply changing one dynamic load command: namely the LC_RPATH specifying the @rpath path.
  2. Second, the dynamic library might contain several LC_RPATH commands, and thus specify several different paths into which every @rpath keyword should be resolved until the requested library is finally found.

install_name_tool: Actually changing dynamic load commands

To actually change the dynamic load commands in executable, install_name_tool is used. Working with the tool is nicely covered in this article and in this case, man install_name_tool is also helpful. Two kinds of changes to paths in dynamic load commands are important:

Changing paths directly in LC_LOAD_DYLIB commands

install_name_tool -change <old_path> <new_path> <executable_or_dylib>

This command will simply find in the specified executable (<executable_or_dylib>) the LC_LOAD_DYLIB command which contains <old_path> and replace the string with <new_path>. The <old_path> must be entered exactly as it is present in LC_LOAD_DYLIB command. After this, the executable will now look for the dynamic library at different path.

Changing @rpaths by altering LC_RPATH commands

To alter executable @rpaths, a different approach is needed. Three different commands are used to:

  1. Add new @rpath to executable:
    install_name_tool -add_rpath <new_path> <executable_or_dylib>
  2. Delete old @rpath from executable:
    install_name_tool -delete_rpath <old_path> <executable_or_dylib>
  3. Change (replace) existing @rpath in executable:
    install_name_tool -rpath <old_path> <new_path> <executable_or_dylib>

Addendum: Changing library identification name path by altering LC_ID_DYLIB commands

Sometimes you will spot a LC_ID_DYLIB command under otool l output, which contains a path also listed in the output of otool L. However, you won't be able to change it using the change subcommand. LC_ID_DYLIB command adds a specific and important path to the library:

Any clients that link against the *.dylib will record the path specified in the LC_ID_DYLIB load command as the path at which to find the *.dylib at runtime. This is useful as the path where the *.dylib lives at build time is often not where it will be found at runtime.9)

To alter this identification path, use the id subcommand:

install_name_tool -id <new_identification_name_path> <dylib>

Qt-specific debugging of missing dependencies

Sometimes when an executable specifically created in Qt fails to run, no error message is displayed. When you want to debug these executables, you need to turn on additional debug messages for Qt frameworks and plugins by setting up QT_DEBUG_PLUGINS variable.

Supplying this command and then running the app will show all the info needed in these cases:

export QT_DEBUG_PLUGINS=1
./YOUR_APP

Note: this is taken from this source.

Comments

blog/odborny/2025-01-09-tweaking_apps_executables_and_dynamic_libraries_with_install_name_tool_and_otool.txt · Posledná úprava: 2025/01/19 18:42 od Róbert Toth