Analyzing core dumps¶
The core
subcommand is used to analyze the status of a core dump file. Analyzing core files is
very similar to analyzing processes but there are some differences, as the core file does not
contain the totality of the memory that was valid when the program was live. In most cases, this
makes no difference, as PyStack will try to adapt automatically. However, in some cases, you will need to
specify extra command line options to help PyStack locate the information it needs. When analyzing
cores, there are several options available:
usage: pystack core [-h] [-v] [--no-color] [--native] [--native-all]
[--locals] [--exhaustive]
[--lib-search-path LIB_SEARCH_PATH | --lib-search-root LIB_SEARCH_ROOT]
core [executable]
Positional Arguments¶
- core
The path to the core file
- executable
(Optional) The path to the executable of the core file
Named Arguments¶
- -v, --verbose
Default:
0
- --no-color
Deactivate colored output
Default:
False
- --native
Include the native (C) frames in the resulting stack trace
Default:
NativeReportingMode.OFF
- --native-all
Include native (C) frames from threads not registered with the interpreter (implies --native)
Default:
NativeReportingMode.OFF
- --locals
Show local variables for each frame in the stack trace
Default:
False
- --exhaustive
Use all possible methods to obtain the Python stack info (may be slow)
Default:
False
- --lib-search-path
List of paths to search for shared libraries loaded in the core. Paths must be separated by the ‘:’ character
- --lib-search-root
Root directory to search recursively for shared libraries loaded into the core.
In most cases, you just need to provide the location of the core to use PyStack with core dump files:
$ pystack core ./the_core_file
Using executable found in the core file: /usr/bin/python3.8
Core file information:
state: t zombie: True niceness: 0
pid: 570 ppid: 1 sid: 1
uid: 0 gid: 0 pgrp: 570
executable: python3.8 arguments: python3.8
The process died due receiving signal SIGSTOP
Traceback for thread 570 [] (most recent call last):
(Python) File "/test.py", line 19, in <module>
first_func({1: None}, [1,2,3])
(Python) File "/test.py", line 7, in first_func
second_func(x, y)
(Python) File "/test.py", line 12, in second_func
third_func(x, y)
(Python) File "/test.py", line 16, in third_func
time.sleep(1000)
Pystack can automatically extract core dumps from .gz files: pystack core ./archived_core_file.gz
To learn more about the different options you can use to customize what is reported, check the Customizing the reports section.
Providing the executable¶
As the core file doesn’t contain all the memory that was available in the original process, there is some information that needs to be obtained from the original Python executable that was running. If you just provide the location of the core file on the command line, PyStack will try to automatically locate the executable for you:
$ pystack core ./the_core_file
Using executable found in the core file: /usr/bin/python3.8
If the location of the executable is known, it can also by provided manually by passing it as the second argument:
$ pystack core ./the_core_file /usr/bin/python3.8
In most cases, providing just the location of the core file will work and PyStack will be able to do a successful analysis, but is possible that the executable being identified automatically is not the correct one. This can happen if the process was launched through a script with a shebang or using some form of indirection. For instance:
$ pystack core ./pytest_core
Using executable found in the core file: /usr/bin/pytest
Core file information:
state: t zombie: True niceness: 0
pid: 770 ppid: 1 sid: 1
uid: 0 gid: 0 pgrp: 770
executable: python3.8 arguments: /usr/bin/python3.8
The core file seems to have been generated on demand (the process did not crash)
💀 Unable to find maps for the executable /usr/bin/pytest. These are the available executable memory maps: [dso], /usr/bin/python3.8 💀
This happens because /usr/bin/pytest
is actually a shell script that
will be executed using a shebang and not the actual Python executable (located
in /usr/bin/python3.8
):
$ file /usr/bin/pytest
/usr/bin/pytest: Python script, ASCII text executable
In this case, PyStack complains that it cannot match the memory maps in the core file to the the provided executable and is providing the list of memory maps that are available in the core:
💀 Unable to find maps for the executable /usr/bin/pytest. These are the available executable memory maps: [dso], /usr/bin/python3.8 💀
Note
PyStack matches the memory maps of the executable (whether provided manually
or detected automatically) by the base name of the file. This means
that if the there is a memory map for /usr/bin/python3.8
and the
provided (or automatically detected) executable is
/usr/other_location/python3.8
then pystack
will match them
because the base name is the same (python3.8
) but if the provided
executable is /usr/bin/python3.7
then pystack
will not match
them because python3.7 != python3.8
.
To solve this, you must provide the path to the Python binary that was
used to execute the original process. We can use the list of available maps to
identify possible candidates for the executable, as it must match one of the
available memory maps that pystack
includes in the error message. In
addition to the list of the memory maps, notice that pystack
also includes
some useful information that greatly helps identifying the correct executable:
Core file information:
state: t zombie: True niceness: 0
pid: 770 ppid: 1 sid: 1
uid: 0 gid: 0 pgrp: 770
executable: python3.8 arguments: /usr/bin/python3.8
Here we can easily see what executable was used (python3.8
) and how it was
invoked. In this particular example is easy to also use the provided memory
maps because the Python executable is the only map that is a file among the
ones provided by pystack
: /usr/bin/python3.8
. To indicate to
pystack
that this is the executable we just need to pass it as the second
argument of the command line invocation:
$ pystack core ./pytest_core /usr/bin/python3.8
Core file information:
state: t zombie: True niceness: 0
pid: 770 ppid: 1 sid: 1
uid: 0 gid: 0 pgrp: 770
executable: python3.8 arguments: /usr/bin/python3.8
The core file seems to have been generated on demand (the process did not crash)
Traceback for thread 770 [Has the GIL] (most recent call last):
(Python) File "/usr/bin/pytest", line 8, in <module>
sys.exit(console_main())
(Python) File "/usr/lib/python3.8/site-packages/_pytest/config/__init__.py", line 185, in console_main
code = main()
(Python) File "/usr/lib/python3.8/site-packages/_pytest/config/__init__.py", line 162, in main
ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
...
Core file information¶
To help users learn details of the program that generated the core file, PyStack will try to display additional information based on various kernel data structures at the time of the crash:
Core file information:
state: t zombie: True niceness: 0
pid: 770 ppid: 1 sid: 1
uid: 0 gid: 0 pgrp: 770
executable: python3.8 arguments: /usr/bin/python3.8 -c "print('Hello')"
Warning
The information provided in the “Core file information” section has a maximum size of 80 characters so it can be incomplete in some cases where large paths or command line arguments are involved.
This information displayed can include the following attributes:
state: A letter describing the state of a given process. It can be one of:
d
— uninterruptible sleep (usually IO):i
— idle kernel threadr
— running or runnable (on run queue)s
— interruptible sleep (waiting for an event to complete)t
— stopped by job control signalt
— stopped by debugger during the tracingw
— paging (not valid since the 2.6.xx kernel)x
— dead (should never be seen)z
— defunct (“zombie”) process, terminated but not reaped by its parent
zombie: A boolean described if the process was in a zombie state (terminated but not reaped by its parent) at the time of the crash.
niceness: The value of the “niceness” parameter that represents the CPU priority in the scheduler.
pid: The PID of the process.
ppid: The PID of the parent of the process.
sid: The Session ID of the process.
uid: The User ID of the process.
gid: The Group ID of the process.
pgrp: The process group.
executable: The base name of the executable that was used to start the process.
arguments: The command line invocation of the process, truncated at 80 characters.
Origin of the core file¶
Some times core file are not generated because the program crashed, and this
can be a source of confusion as analyzing the stack trace report looking for
crashes will be a futile task because the process could have been in any
arbitrary healthy state. Even if the process has indeed crashed, knowing if it
was planned or not can be very important to perform a successful analysis. For
this reason, pystack
will try to display why the core was generated.
Depending on the kernel version and the available information in the core file,
it can show several cases:
The core file was generated on demand:
The core file seems to have been generated on demand (the process did not crash)
The core file was generated because some user sent a killing signal:
The process died due receiving signal SIGBUS sent by pid 23
The core file was generated because it received a segmentation fault signal:
The process died due a segmentation fault accessing address: 0x75bcd15
Using this information, is possible to make sense of the displayed stack traces when hunting for a specific problem.
Analyzing core files in a different machine¶
In general, analyzing core files in the same machine where they were generated will almost always yield a successful analysis using the default command line options but analyzing core files in a different machine can lead to complications. The main complication is that the shared libraries that were mapped into the process and that are referred by the core are either not available on the machine where the analysis is being performed or are different (have a different build ID) than the ones that were mapped into the process that generated the core. In this case, when analyzing the core file you will see the following warnings:
$ pystack core ./the_core /path/to/the_executable
...
WARNING(process_core): Failed to locate /tmp/bundle/python/lib/python3.8/lib-dynload/_heapq.cpython-38-x86_64-linux-gnu.so
WARNING(process_core): Failed to locate /tmp/bundle/binary-dependencies/libfreebl3.so
WARNING(process_core): Failed to locate /tmp/bundle/binary-dependencies/libcrypt.so.1
WARNING(process_core): Failed to locate /tmp/bundle/binary-dependencies/libpython3.8.so.1.0
...
Caution
Note that is possible that pystack
can still generate a valid report
even if it fails to find some of the shared libraries that were linked into
the process, so the presence of these warnings is not an error condition.
Failing to provide shared libraries involved in the stack trace can make
the --native
and --native-all
options not work properly.
This indicates that PyStack has failed to locate some shared libraries that were loaded into the process. Loaded shared libraries’ paths are recorded in the core file, but if the core file is moved to a different machine, or if libraries on the machine are updated or removed, then PyStack won’t be able to find the versions of the libraries that it needs to understand the core file.
Fortunately, if you have access to the shared libraries that were originally used, it is possible to point PyStack to a new location for these shared libraries. There are two options for pointing PyStack at libraries in different locations than what was recorded in the core file:
--lib-search-path
accepts a colon-separated list of directories to search for libraries in (like the format of thePATH
environment variable). For example, if you’ve copied a PyInstaller application to a new directory, you might use:$ pystack core ./the_core ./the_app/appname/appname --lib-search-path="./the_app/appname:./the_app/appname/lib-dynload" Traceback for thread 1340 [] (most recent call last): (Python) File "/src/tests/integration/single_thread_program.py", line 20, in <module> first_func() (Python) File "/src/tests/integration/single_thread_program.py", line 6, in first_func second_func() (Python) File "/src/tests/integration/single_thread_program.py", line 10, in second_func third_func() (Python) File "/src/tests/integration/single_thread_program.py", line 17, in third_func time.sleep(1000)
Note
Directories are searched in the order they occur in the list, and libraries are matched by file name. If two directories in the search path both contain a file with the same name as a required shared library, the library from the directory that is listed earlier in the search path will be used.
--lib-search-root
allows providing a directory to be searched recursively for shared libraries. PyStack will automatically construct a search path containing every directory with shared libraries under that search root, then use that constructed path as though it was given as--lib-search-path
. For example:$ pystack core ./the_core ./the_app/appname/appname --lib-search-root=./the_app Traceback for thread 1340 [] (most recent call last): (Python) File "/src/tests/integration/single_thread_program.py", line 20, in <module> first_func() (Python) File "/src/tests/integration/single_thread_program.py", line 6, in first_func second_func() (Python) File "/src/tests/integration/single_thread_program.py", line 10, in second_func third_func() (Python) File "/src/tests/integration/single_thread_program.py", line 17, in third_func time.sleep(1000) The ``--lib-search-root`` option is especially useful for analyzing core files generated by self contained applications, such as those produced by PyInstaller's ``--onedir`` mode). When an application is bundled together with its binary dependencies, you only need to provide PyStack with the root folder and it will automatically find all of the bundled shared libraries.
Warning
The --lib-search-root
option adds directories to the library search path
in lexicographic order. If a file with the same name as a shared library
PyStack needs to load is found in two different directories under the search
root, the wrong one may be used. When the order in which directories under
the root are searched matters, use --lib-search-path
instead.
Tip
You can see the library search path that --lib-search-root
has
constructed when running in -v
verbose mode.
Analyzing core files with insufficient information¶
In some rare scenarios is possible that PyStack
won’t have enough information to show the Python stack trace nor the native
stack trace (missing symbols, missing information in the executable, corrupt
core file information, etc). In these cases, is still possible to obtain the
Python stack trace of the core file by using the --exhaustive
option. This
will trigger a more complete search for the interpreter information by
analyzing raw memory but will normally be slower and it will take a time
proportional to the core file size, as all the memory needs to be analysed to
do that.
Tip
When using --exhaustive
make sure you have the core file in a fast
file-system (not NFS or docker mount points) to speed up the analysis.