Python 里面嵌入 ctypes 调试经验
Tofloor
poster avatar
ManateeLazyCat
deepin
2011-12-22 08:14
Author
由于软件中心用的是 gtk-2.0 ,由于 gi (pygtk+ 3.0 的主要绑定方式) 不能和2.0的 gobject 库同时导入, 所以软件中心只有通过 ctypes 来调用一些 2.0 还没有绑定的 APIs。

比如一个浏览器要实现 cookie 的读取/保存以及代理功能, 就需要类似下面的代码:
  1. from constant import *
  2. from lang import __, getDefaultLanguage
  3. from utils import *
  4. import ctypes
  5. import gobject
  6. import glib
  7. import gtk
  8. import os, webkit, webbrowser
  9. import utils
  10. libgobject = ctypes.CDLL('libgobject-2.0.so.0')
  11. libwebkit = ctypes.CDLL('libwebkitgtk-1.0.so.0')
  12. libsoup = ctypes.CDLL('libsoup-2.4.so.1')
  13. class Browser(webkit.WebView):
  14.     '''Browser.'''
  15.        
  16.     def __init__(self, uri):
  17.         '''Init browser.'''
  18.         # Init.
  19.         webkit.WebView.__init__(self)
  20.         
  21.         # Get default session.
  22.         self.session = libwebkit.webkit_get_default_session()
  23.         
  24.         # Init cookie.
  25.         self.initCookie()
  26.         
  27.         # Init proxy.
  28.         self.initProxy()
  29.         # Load uri.
  30.         self.load_uri(uri)
  31.             
  32.     def initCookie(self):
  33.         '''Init cookie.'''
  34.             if not os.path.exists(COOKIE_FILE):
  35.                     os.mknod(COOKIE_FILE)
  36.         soupCookie = libsoup.soup_cookie_jar_text_new(COOKIE_FILE, False)
  37.         libgobject.g_object_set(self.session, 'add-feature', soupCookie, None)
  38.         
  39.     def initProxy(self):
  40.         '''Init proxy.'''
  41.         proxyString = utils.parseProxyString()
  42.         if proxyString != None:
  43.             soupUri = libsoup.soup_uri_new(str(proxyString))
  44.             libgobject.g_object_set(self.session, 'proxy-uri', soupUri, None)
Copy the Code

但是软件中心在利用上面的代码进行浏览评论的时候会间歇性的 crash, 而且外部是用了 try ... except 捕获异常的, 所以第一反映是 C-level 的 crash 导致 Python 捕获不了异常。

像这种 C-level 的 crash, Python本身是无能为力的, 所以为了捕获这种异常就要用到 gdb.

下面是用 gdb 调试软件中心的方法:

1. cd 到软件中心目录并用 gdb 启动python
  1. cd ./deepin-software-center/src && sudo gdb python
Copy the Code
这个时候会在终端中看到一下内容:

GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /usr/bin/python...(no debugging symbols found)...done.
(gdb)

2. 用 gdb 启动软件中心:
  1. r ./deepin-software-center.py
Copy the Code
r 在 gdb 是表示运行的意思 (run)

3. 这个时候就可以像正常使用软件中心, 一旦软件中心发生了 C-level 的 crash 就会被 gdb 捕获到, 比如下面的错误:
  1. Program received signal SIGSEGV, Segmentation fault.
  2. 0x00007ffff320d0c5 in ?? () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
Copy the Code

这个一看就是 libgobject 动态库的函数调用引起的 crash, 但是怎样具体定位到特定的代码呢?
只需要在这个时候按一下 Ctrl + c 并键入 bt 就可以得到 crash 的堆栈了:
  1. (gdb) bt
  2. #0  0x00007ffff320d0c5 in ?? () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  3. #1  0x00007ffff321133b in g_object_set_valist () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  4. #2  0x00007ffff3212057 in g_object_set () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  5. #3  0x00007fffee273d54 in ffi_call_unix64 () from /usr/lib/python2.7/lib-dynload/_ctypes.so
  6. #4  0x00007fffee273775 in ffi_call () from /usr/lib/python2.7/lib-dynload/_ctypes.so
  7. #5  0x00007fffee26bef7 in _ctypes_callproc () from /usr/lib/python2.7/lib-dynload/_ctypes.so
  8. #6  0x00007fffee2635f7 in ?? () from /usr/lib/python2.7/lib-dynload/_ctypes.so
  9. #7  0x000000000041ad2a in PyObject_Call ()
  10. #8  0x00000000004b6b9e in PyEval_EvalFrameEx ()
  11. #9  0x00000000004b6d77 in PyEval_EvalFrameEx ()
  12. #10 0x00000000004bcd2d in PyEval_EvalCodeEx ()
  13. #11 0x0000000000448edf in ?? ()
  14. #12 0x000000000041ad2a in PyObject_Call ()
  15. #13 0x000000000043074e in ?? ()
  16. #14 0x000000000041ad2a in PyObject_Call ()
  17. #15 0x0000000000480c73 in ?? ()
  18. #16 0x000000000047c1d1 in ?? ()
  19. #17 0x000000000041ad2a in PyObject_Call ()
  20. #18 0x00000000004b6b9e in PyEval_EvalFrameEx ()
  21. #19 0x00000000004bd325 in PyEval_EvalCodeEx ()
  22. #20 0x0000000000448edf in ?? ()
  23. #21 0x000000000041ad2a in PyObject_Call ()
  24. #22 0x000000000043074e in ?? ()
  25. #23 0x000000000041ad2a in PyObject_Call ()
  26. #24 0x0000000000480c73 in ?? ()
  27. #25 0x000000000047c1d1 in ?? ()
  28. #26 0x000000000041ad2a in PyObject_Call ()
  29. #27 0x00000000004b6b9e in PyEval_EvalFrameEx ()
  30. #28 0x00000000004b6d77 in PyEval_EvalFrameEx ()
  31. #29 0x00000000004b6d77 in PyEval_EvalFrameEx ()
  32. #30 0x00000000004bd325 in PyEval_EvalCodeEx ()
  33. #31 0x0000000000448edf in ?? ()
  34. #32 0x000000000041ad2a in PyObject_Call ()
  35. #33 0x00000000004b5d76 in PyEval_CallObjectWithKeywords ()
  36. #34 0x00007ffff3465438 in ?? () from /usr/lib/python2.7/dist-packages/gobject/_gobject.so
  37. #35 0x00007ffff320a0a4 in g_closure_invoke () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  38. #36 0x00007ffff321c02a in ?? () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  39. #37 0x00007ffff3225483 in g_signal_emit_valist () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  40. #38 0x00007ffff3225852 in g_signal_emit () from /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
  41. #39 0x00007ffff2763dc1 in ?? () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
  42. #40 0x00007ffff2647a23 in gtk_propagate_event () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
  43. #41 0x00007ffff2647d83 in gtk_main_do_event () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
  44. #42 0x00007ffff22bc09c in ?? () from /usr/lib/x86_64-linux-gnu/libgdk-x11-2.0.so.0
  45. #43 0x00007ffff3f03a5d in g_main_context_dispatch () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
  46. #44 0x00007ffff3f04258 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
  47. #45 0x00007ffff3f04792 in g_main_loop_run () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
  48. #46 0x00007ffff2646db7 in gtk_main () from /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0
  49. #47 0x00007ffff2cfa056 in ?? () from /usr/lib/python2.7/dist-packages/gtk-2.0/gtk/_gtk.so
  50. #48 0x00000000004b7114 in PyEval_EvalFrameEx ()
  51. #49 0x00000000004b6d77 in PyEval_EvalFrameEx ()
  52. #50 0x00000000004bcd2d in PyEval_EvalCodeEx ()
  53. #51 0x00000000004bd802 in PyEval_EvalCode ()
  54. #52 0x00000000004dcc22 in ?? ()
  55. #53 0x00000000004dd7e4 in PyRun_FileExFlags ()
  56. #54 0x00000000004de2ee in PyRun_SimpleFileExFlags ()
  57. #55 0x00000000004ee6dd in Py_Main ()
  58. #56 0x00007ffff699e30d in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6
  59. #57 0x000000000041cb69 in _start ()
Copy the Code

堆栈的阅读要从下往上读, 一般最上面的几行就是导致 crash 的函数, 比如上面的我们可以知道当软件中心调用 g_object_set 的函数导致的。
这样就好定位了, g_object_set 的就两行, 一行设置 cookie , 一行设置 proxy, 参数的类型没有错, 简单的推测应该是 g_object_set 设置了一个规定范围之外的值导致 crash 的, 这样我们修改一下代码在 g_object_set 之前就打印一下值:
  1.     def initCookie(self):
  2.         '''Init cookie.'''
  3.             if not os.path.exists(COOKIE_FILE):
  4.                     os.mknod(COOKIE_FILE)
  5.         soupCookie = libsoup.soup_cookie_jar_text_new(COOKIE_FILE, False)
  6.         print "Soup Cookie: %s" % (soupCookie)
  7.         libgobject.g_object_set(self.session, 'add-feature', soupCookie, None)
  8.         
  9.     def initProxy(self):
  10.         '''Init proxy.'''
  11.         proxyString = utils.parseProxyString()
  12.         if proxyString != None:
  13.             soupUri = libsoup.soup_uri_new(str(proxyString))
  14.             print "Soup Uri: %s" % (soupUri)
  15.             libgobject.g_object_set(self.session, 'proxy-uri', soupUri, None)
Copy the Code

最后发现是因为 soupCookie 的值是一个负数导致
  1. libgobject.g_object_set(self.session, 'add-feature', soupCookie, None)
Copy the Code
直接 crash 了。

到这里, 问题的原因知道了, 加一个条件进行保护一下:
  1.     def initCookie(self):
  2.         '''Init cookie.'''
  3.             if not os.path.exists(COOKIE_FILE):
  4.                     os.mknod(COOKIE_FILE)
  5.         soupCookie = libsoup.soup_cookie_jar_text_new(COOKIE_FILE, False)
  6.         print "Soup Cookie: %s" % (soupCookie)
  7.         if soupCookie < 0:
  8.             raise Exception("Incorrect cookie value: %s" % (soupCookie))
  9.         else:
  10.             libgobject.g_object_set(self.session, 'add-feature', soupCookie, None)
Copy the Code

到此就是对 ctypes 抛出异常进行诊断的整个流程, 希望大家以后遇到这类问题可以参考一下。

PS: gtk+ 3.0 是不存在这种问题的,因为 gtk+ 3.0 的可以直接调用 gi 相应的API并且在Python里面已经可以很好的捕捉异常。
Reply Favorite View the author
All Replies
lovesnow
deepin
2011-12-22 08:23
#1
:  :  :  :   嗯  被我刷出来了
Reply View the author
yfwz100
deepin
2012-01-06 16:32
#2
现在深度主要的开发工具还是首选Gtk+ 2.0的吗?
Reply View the author
ManateeLazyCat
deepin
2012-01-06 20:15
#3
现在深度主要的开发工具还是首选Gtk+ 2.0的吗?
开发工具是 Emacs 或者 Vi, 图形开发库, 软件中心用的是 GTK+ 2.0, 以后的桌面开发会用 Python/JS/GTK+/Clutter 的技术标准。
Reply View the author
yfwz100
deepin
2012-01-06 20:56
#4
现在社区开发着的项目寄存在哪里呢?
Reply View the author
ManateeLazyCat
deepin
2012-01-07 11:00
#5
现在社区开发着的项目寄存在哪里呢?
https://github.com/linuxdeepin-packages
https://github.com/manateelazycat
https://github.com/lovesnow

第一个是官方打包仓库, 后面是开发组成员的个人开发仓库。
Reply View the author