代码之家  ›  专栏  ›  技术社区  ›  user1610950

如何处理内存、.so文件名和十六进制偏移量

  •  0
  • user1610950  · 技术社区  · 8 年前

    不要因为这个而激怒我,但这是真的。我正在编写一个运行时间很长的多线程python应用程序,通常运行2-3个小时,有10个进程。这台机器不慢,只是计算量很大。

    问题是,由于外部工具的原因,有时应用程序会挂起85-90%。

    我已经将这个测试分解为几个小部分,然后可以成功运行,但长时间运行的程序挂起。

    例如,假设我必须分析一个有100000000项的列表中的一些数据。

    将其分解为20个5000000,列出了所有较小的部分。

    试图完成这项1亿美元的计划,它就要结束了。我使用一些我无法改变的外部工具,所以我只是想看看发生了什么。

    我设置了Dtrace并运行

    sudo dtrace -n 'syscall:::entry / execname == "python2.7" / { @[ustack()] = count() }'
    

    在我的程序挂起时,我得到如下代码示例的输出。

              libc.so.7`__sys_recvfrom+0xa
              _socket.so`0x804086ecd
              _socket.so`0x8040854ac
              libpython2.7.so.1`PyEval_EvalFrameEx+0x52d7
              libpython2.7.so.1`PyEval_EvalCodeEx+0x665
              libpython2.7.so.1`0x800b3317d
              libpython2.7.so.1`PyEval_EvalFrameEx+0x4e2f
              libpython2.7.so.1`0x800b33250
              libpython2.7.so.1`PyEval_EvalFrameEx+0x4e2f
              libpython2.7.so.1`PyEval_EvalCodeEx+0x665
              libpython2.7.so.1`0x800abb5a1
              libpython2.7.so.1`PyObject_Call+0x64
              libpython2.7.so.1`0x800aa3855
              libpython2.7.so.1`PyObject_Call+0x64
              libpython2.7.so.1`PyEval_EvalFrameEx+0x4de2
              libpython2.7.so.1`PyEval_EvalCodeEx+0x665
              libpython2.7.so.1`0x800abb5a1
              libpython2.7.so.1`PyObject_Call+0x64
              libpython2.7.so.1`0x800aa3855
              libpython2.7.so.1`PyObject_Call+0x64
    

    代码只是重复了一遍又一遍。我试图调查Dtrace python探测器,但从周二开始,这些探测器似乎被两方面破坏了,所以这可能是我能得到的最接近的。

    我的问题是,我有一个模糊的想法 libpython2.7.so.1 是保存函数的共享库 pyObject_Call 十六进制偏移量为0x64

    对吗?

    我怎么才能破译这个?我甚至不知道该怎么称呼它,这样我就可以在谷歌上搜索答案或指南。

    1 回复  |  直到 8 年前
        1
  •  2
  •   Community CDub    8 年前

    你应该从阅读开始 Showing the stack trace from a running Python application .

    你的具体 问题是关于DTrace的ustack()操作的解释,以及 因此,这个答复可能超出了您的需要。这是因为 DTrace的设计原则是显示系统的精确状态。 因此,即使您对您的 程序,DTrace正在揭示其底层实现。

    您提供的输出是一个堆栈,这是一种 描述线程在其 处决例如,如果您有代码

    void c(void) { pause(); }
    void b(void) { c(); }
    void a(void) { b(); }
    

    当执行在pause()内时,您请求一个堆栈,然后 你 可以 看到类似

    pause()
    c()
    b()
    a()
    

    您使用的任何工具都将找到当前指令及其 找到“返回地址”之前的封闭函数,即 该函数最终将返回的点;重复这个 过程生成一个堆栈。因此,尽管应该读取堆栈 从上到下作为一系列返回地址,通常是 作为一系列呼叫者从另一个方向读。注意 程序对应方式的微妙之处 指令的汇编意味着第二种解释 有时可能会产生误导。

    为了扩展上面的示例,a()、b()和c()可能是 所有人都在同一个图书馆里——可能有 在其他库中具有相同名称的函数。因此 用于为每个函数显示其所指向的对象 属于。因此,上面的堆栈可能会变成

    libc.so`pause()
    libfoo.so`c()
    libfoo.so`b()
    libfoo.so`a()
    

    这在某种程度上有助于开发人员识别 程序以特定状态结束:libfoo中的函数c() 它被称为pause()。然而,还有更多的工作要做:如果c()是 看起来像

    void c() {
        pause();
        pause();
    }
    

    那么程序正在等待对pause()的哪个调用?

    函数a()、b()和c()将是序列 通常会占用的连续区域的指令 记忆力调用其中一个函数只需要 记录完成后返回的位置(即返回 地址),然后跳到与之对应的存储器地址 函数的开始。函数的起始地址和大小为 记录在嵌入对象的“符号表”中;是 通过读取此表,调试器能够找到函数 包含给定位置(如返回地址)的。因此 具体点 在内部 通常以十六进制表示。因此,一个更好的 上面堆栈的版本可能是

    libc.so`pause()+0x12
    libfoo.so`c()+0x42
    libfoo.so`b()+0x12
    libfoo.so`a()+0x12
    

    此时,开发人员可以在libfoo.so上使用“反汇编程序” 显示c()中的指令;与c()的比较 源代码将允许他透露特定的行 已调用pause()。

    在结束对堆栈的描述之前,值得 再观察一次。如果存在足够的“调试” 在libfoo这样的库中,更好的调试器将能够 要进一步显示,请输入源代码文件名和 中每个“帧”的行号而不是十六进制偏移量 堆栈。

    现在,回到你问题中的堆栈, libpython(2.7.so.1)是一个函数执行任务的库 执行Python脚本。Python脚本中的函数是 动态转换为可执行指令,所以我的猜测是 碎片

    libpython2.7.so.1`0x800b33250
    libpython2.7.so.1`PyEval_EvalFrameEx+0x4e2f
    libpython2.7.so.1`PyEval_EvalCodeEx+0x665
    

    意味着PyEval_EvalFrameEx()是libpython中的功能 调用Python函数(即,编写的 Python),它驻留在地址0x800b33250.A附近的内存中 简单的调试器可以看到这个地址属于libpython,但是 在库的符号表中找不到相应的条目; 没有选择,它只打印“原始”地址。

    因此,您需要查看Python脚本,看看它是什么 但不幸的是,没有迹象表明 中的功能 蟒蛇 堆栈的组件。

    有几种方法可以继续。首先是找到 libpython的版本(如果存在),带有 "DTrace helper" 这 是一些额外的功能,允许DTrace查看 Python程序本身,除了周围 实施结果是,每个Python框架都是 用Python源代码中的相应点进行注释。

    另一个,如果您在Solaris上,则使用pstack(1);这 对Python的本地支持。

    还值得指出的是,您的dtrace调用将显示 您看到的所有堆栈,按流行度排序,无论何时 程序“python2.7”进行系统调用。根据你的描述, 这可能不是你想要的。如果你想理解 挂起的行为,那么您可能想从 python2.7进程的单个快照 在 悬挂 .