开发环境
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
没有加载。
解决方案
文末还有解决方案2
解决办法是找到 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://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.o
和 b.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先加载。
参考资料:
解决方案2
- 首先使用 homebrew 安装
mysql-client
brew install mysql-client
- 然后,把 mysql-client 添加到环境变量中
export PATH=
"/usr/local/opt/mysql-client/bin:$PATH"
export LIBRARY_PATH=
"$LIBRARY_PATH:/usr/local/opt/openssl/lib/"
- 最后安装
mysqlclient
pip3 install mysqlclient