L'utilisation otool (récursive) pour trouver les bibliothèques partagées nécessaires pour une application
-
19-09-2019 - |
Question
J'ai une application Cocoa qui otool utilise pour trouver des bibliothèques partagées nécessaires qu'une application a besoin pour fonctionner correctement. Par exemple, supposons que je lance otool -L sur une application qui utilise QTKit.framework. Je reçois une liste des bibliothèques partagées utilisées par le programme (y compris les cadres de base comme Cocoa.framework et AppKit.framework):
/System/Library/Frameworks/QTKit.framework/Versions/A/QTKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 476.0.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 949.0.0)
..... and so on for a bunch of other frameworks
Ce qui montre que l'application utilise QTKit.framework. Toutefois, si je l'utilise « otool -L » à nouveau sur le binaire pour QTKit.framework (/System/Library/Frameworks/QTKit.framework/Versions/A/QTKit) Je reçois ceci:
/System/Library/Frameworks/QTKit.framework/Versions/A/QTKit (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/CoreMedia.framework/Versions/A/CoreMedia (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/MediaToolbox.framework/Versions/A/MediaToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/VideoToolbox.framework/Versions/A/VideoToolbox (compatibility version 1.0.0, current version 1.0.0)
/System/Library/PrivateFrameworks/CoreMediaIOServices.framework/Versions/A/CoreMediaIOServices (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 751.0.0)
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit (compatibility version 45.0.0, current version 1038.0.0)
/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit (compatibility version 1.0.0, current version 275.0.0)
/System/Library/Frameworks/QuickTime.framework/Versions/A/QuickTime (compatibility version 1.0.0, current version 1584.0.0)
/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore (compatibility version 1.2.0, current version 1.6.0)
/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/HIToolbox (compatibility version 1.0.0, current version 435.0.0)
/usr/lib/libstdc++.6.dylib (compatibility version 7.0.0, current version 7.9.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 123.0.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 227.0.0)
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 44.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 550.0.0)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 38.0.0)
/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo (compatibility version 1.2.0, current version 1.6.0)
Cela montre une charge plus de cadres que la sortie otool d'origine sur l'application binaire montré. Est-il possible d'avoir otool récursivement, ce qui signifie qu'il saisit les cadres que les besoins d'applications, puis se et va chercher dans chacun de ces cadres pour les dépendances?
La solution
Non, vous devez exécuter otool à plusieurs reprises, ou incorporer son code d'analyse syntaxique ( ici ). Ne pas oublier la manipulation @executable_path
.
Ici, il est en Python (sans @executable_path
, canonicalisation, ou les noms de fichiers avec-espaces pris en charge), car cela était plus facile que d'essayer de débogage pseudocode:
import subprocess
def otool(s):
o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE)
for l in o.stdout:
if l[0] == '\t':
yield l.split(' ', 1)[0][1:]
need = set(['/Applications/iTunes.app/Contents/MacOS/iTunes'])
done = set()
while need:
needed = set(need)
need = set()
for f in needed:
need.update(otool(f))
done.update(needed)
need.difference_update(done)
for f in sorted(done):
print f
Autres conseils
Voici ma solution que j'utilise pour fixer la sortie macdeployqt
lors de l'utilisation des bibliothèques Homebrew installé. Ce que j'ai trouvé que macdeployqt
fait un bon travail de mettre les dylibs dans le dossier-cadre, mais il ne parvient pas à corriger les chemins.
https://github.com/jveitchmichaelis/deeplabel/blob/master /fix_paths_mac.py
J'ai modifié le script de Nicholas d'être un peu plus facile à utiliser - il corrige @executable_path
, @rpath
et @loader_path
. Ce n'est pas exactement le code de production, mais il a laissé me exécuter des applications sur d'autres Mac sans dépendances déjà installés.
Exécuter avec: python fix_paths_mac.py ./path/to/your.app/Contents/MacOS/your_exe
. à savoir le pointer vers le binaire à l'intérieur d'un paquet d'application et il va comprendre le reste.
Je suis supposé que la plupart des problèmes viennent de choses liées à /usr/local
. Donc, si le code détecte qu'il ya une dépendance qui pointe vers un fichier dans /usr/local
, il va fixer les chemins correctement. Vous pouvez modifier l'instruction pass
copier dans un fichier si ce n'est pas dans le dossier Frameworks
, mais je n'ai pas rencontré une situation où il y a un manque dylib, il est juste liée mal.
import subprocess
import os
import sys
from shutil import copyfile
executable = sys.argv[1]
app_folder = os.path.join(*executable.split('/')[:-3])
content_folder = os.path.join(app_folder, "Contents")
framework_path = os.path.join(content_folder, "Frameworks")
print(executable)
print("Working in {} ".format(app_folder))
def file_in_folder(file, folder):
return os.path.exists(os.path.join(folder, file))
def otool(s):
o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE)
for l in o.stdout:
l = l.decode()
if l[0] == '\t':
path = l.split(' ', 1)[0][1:]
if "@executable_path" in path:
path = path.replace("@executable_path", "")
# fudge here to strip /../ from the start of the path.
path = os.path.join(content_folder, path[4:])
if "@loader_path" in path:
path = path.replace("@loader_path", framework_path)
if "@rpath" in path:
path = path.replace("@rpath", framework_path)
dependency_dylib_name = os.path.split(path)[-1]
if "usr/local" in path:
if app_folder in s:
print("Warning: {} depends on {}".format(s, path))
if file_in_folder(dependency_dylib_name, framework_path):
print("Dependent library {} is already in framework folder".format(dependency_dylib_name))
print("Running install name tool to fix {}.".format(s))
if dependency_dylib_name == os.path.split(s)[-1]:
_ = subprocess.Popen(['install_name_tool', '-id', os.path.join("@loader_path", dependency_dylib_name), s], stdout=subprocess.PIPE)
_ = subprocess.Popen(['install_name_tool', '-change', path, os.path.join("@loader_path", dependency_dylib_name), s], stdout=subprocess.PIPE)
else:
# Potentially you could copy in the offending dylib here.
pass
yield path
need = set([executable])
done = set()
while need:
needed = set(need)
need = set()
for f in needed:
need.update(otool(f))
done.update(needed)
need.difference_update(done)