开发环境

macOS Mojave : 10.14.5
Python : 3.7.3
Django : 2.2.3
MySQL : 8.0.15

问题场景

在创建表结构时出现:

$ python manage.py migrate

Traceback (most recent call last):
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/backends/mysql/base.py", line 15, in <module>
    import MySQLdb as Database
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/MySQLdb/__init__.py", line 18, in <module>
    from . import _mysql
ImportError: dlopen(/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/MySQLdb/_mysql.cpython-37m-darwin.so, 2): Library not loaded: @rpath/libmysqlclient.21.dylib
  Referenced from: /Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/MySQLdb/_mysql.cpython-37m-darwin.so
  Reason: image not found

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "manage.py", line 21, in <module>
    main()
  File "manage.py", line 17, in main
    execute_from_command_line(sys.argv)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/core/management/__init__.py", line 381, in execute_from_command_line
    utility.execute()
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/core/management/__init__.py", line 357, in execute
    django.setup()
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/apps/registry.py", line 114, in populate
    app_config.import_models()
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/apps/config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 728, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/contrib/auth/models.py", line 2, in <module>
    from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/contrib/auth/base_user.py", line 47, in <module>
    class AbstractBaseUser(models.Model):
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/models/base.py", line 117, in __new__
    new_class.add_to_class('_meta', Options(meta, app_label))
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/models/base.py", line 321, in add_to_class
    value.contribute_to_class(cls, name)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/models/options.py", line 204, in contribute_to_class
    self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/__init__.py", line 28, in __getattr__
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/utils.py", line 201, in __getitem__
    backend = load_backend(db['ENGINE'])
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/utils.py", line 110, in load_backend
    return import_module('%s.base' % backend_name)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/django/db/backends/mysql/base.py", line 20, in <module>
    ) from err
django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module.
Did you install mysqlclient?  

我直接看了最后一句,Did you install mysqlclient? Yes, I did.

但其实重点是上面那句话,The above exception was the direct cause of the following exception: ,也就是说from . import _mysql才是报错的源头。

我去查看报错信息最后的源码部分,看到:

try:
    import MySQLdb as Database
except ImportError as err:
    raise ImproperlyConfigured(
        'Error loading MySQLdb module.\n'
        'Did you install mysqlclient?'
    ) from err  

即根源在于不能导入 MySQLdb 这个包,这个包在Python3里安装时使用的是pip install mysqlclient,但导入时应import MySQLdb。而 MySQLdb 包里导致这个报错的根源还是 from . import _mysql 。报错信息:

ImportError: dlopen(/Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/MySQLdb/_mysql.cpython-37m-darwin.so, 2): Library not loaded: @rpath/libmysqlclient.21.dylib
  Referenced from: /Users/pinghong/anaconda3/envs/djan/lib/python3.7/site-packages/MySQLdb/_mysql.cpython-37m-darwin.so
  Reason: image not found

这是因为 libmysqlclient.21.dylib 没有加载。

解决方案

解决办法是找到 libmysqlclient.21.dylib ,再用 sudo ln -s 命令软链接到 /usr/local/lib 下面去。

在终端中输入:

mdfind libmysqlclient | grep .21.

返回:

/usr/local/mysql-8.0.15-macos10.14-x86_64/lib/libmysqlclient.21.dylib

拷贝这个文件地址,使用软链接:

sudo ln -s [刚才查到并拷贝到地址] /usr/local/lib/libmysqlclient.21.dylib

也可以先进入 /usr/local/lib ,再使用软链接:

cd /usr/local/lib
sudo ln -s /path/to/you/install/mysql/lib/libmysqlclient.21.dylib

这样就可以正常导入 MySQLdb 包里,也就可以在 django 中使用 mysqlclient (Python3)迁移数据了。

参考链接:

https://stackoverflow.com/questions/6383310/python-mysqldb-library-not-loaded-libmysqlclient-18-dylib/13421926#13421926

https://github.com/PyMySQL/mysqlclient-python/issues/50


由于基本功不扎实,这个简单的bug从发生到解决我一共花费了2个小时。

其中有一个解决方案是先用 otool -L /Users/pinghong/anaconda3/envs/djdemo/lib/python3.7/site-packages/MySQLdb/_mysql.cpython-37m-darwin.so 来查询依赖的库。关于 otool -L 指令的意思,使用 man otool 可看到:

The  objdump(1)  option  to  display the names and version numbers of the shared libraries that the object file uses, as well as the shared library ID if the file is a
              shared library is -dylibs-used.

需要补充的知识点:ln命令.dylib文件

ln命令

摘自菜鸟教程:

ln命令是一个非常重要命令,它的功能是为某一个文件在另外一个位置建立一个同步的链接。

当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。

语法:

ln [参数][源文件或目录][目标文件或目录]

例如创建软链接:

ln -s log2019.log link2019

相当于 Windows 下的创建快捷方式。

创建硬链接:

ln log2019.log ln2019

.dylib文件

dylib 全称是 dynamic library。即动态链接库,在 Windows 上是 .dll,在 Linux 上是 .so,在 Mac 上是 .dylib。

定义

动态链接库(或者叫动态函数库)是应用程序在运行时引用的动态库文件,以便根据需要执行某些功能。 格式已替换旧的 A.OUT 文件格式。dylib 在程序编译的时候, 并没有被编译进二进制目标代码中, 只有当程序里执行相应的函数才调用该函数库里对应的函数。

.dylib 是 Mach-O 格式,也就是 Mac OS X 下的二进制文件格式。Mac OS X提供了一系列工具,用于创建和访问动态链接库。

编译器 /usr/bin/cc,也就是gcc

汇编器 /usr/bin/as

链接器 /usr/bin/ld

如何生成与查看

首先是生成 module 文件,也就是 .o 文件。这跟一般的 unix 没什么区别。例如 cc -c a.c b.c 就得到 a.ob.o

可以用 ld 来合并 .o 文件,比如 ld -r -o c.o a.o b.o

然后可以用 libtool 来创建动态链接库。 libtool -dynamic -o c.dylib a.o b.o

访问动态链接库的工具:nm

nm libmysqlclient.21.dylib

可以看到导出符号表等等。

另一个常用的工具是 otool,这个是 Mac OS X独有的。比如想看看 libmysqlclient.21.dylib 的依赖关系:

otool -L libmysqlclient.21.dylib

最后说一下我对Framework的理解。Framework是Mac OS X下必不可少的部分,不妨去
看看/System/Library/Frameworks/下面,一大堆Framework。Framework是dylib的进
一步演化,它把头文件、文档、动态链接库等整合成一个有机的目录,类似一种自描
述的方式,这种做法其实在Mac OS X下随处可见。比如应用程序,一般都是一个目录,
譬如/Applications/iTunes.app目录对应应用程序iTunes,双击这个目录即开始执行。
这种做法和Windows下常见的一个exe/dll打天下很不相同。

Framework的创建工具也是libtool,详细用法参考man。

dylib加载顺序

如何查看当前App引用了那些dylib与framework?

查看Mach-O(可执行二进制文件)引用的framework与dylib可以通过两种方式来看。

1. 命令行: otool -L yourApp.app/yourApp
2. 用MachOView视图工具, File->Open->YourApp.app/yourApp

动态库的加载顺序:

1. 首先会加载系统级别的dylib, 目录在设备的`/usr/lib/`, 文件:`libsqlite3.dylib、libc++.1.dylib...`
2. 然后加载系统级别的framework, 目录在设备的`/System/Library/Frameworks`, 文件:`Foundation.framework`
3. 再引入runtime、gcd存放的dylib, 目录在设备的`/usr/lib/`, 文件:`libSystem.B.dylib、libobjc.A.dylib`
3. 再引入自己注入的dylib, `@executable_path/`(目录存放在当前可执行文件底下)

所以逆向的人能hook系统的API也是因为系统的framework先加载。

参考资料

Mac OS X下的动态链接库

dylib浅析

What Is a DYLIB File?

分类: MacPython

发表评论

电子邮件地址不会被公开。 必填项已用*标注