ELF Parsing
Requirements for ELF CPython binary:
.dynsym,.rodata,.textsectionsPy_VersionorPy_GetVersionsymbol available in.dynsym_PyThreadState_GetCurrentor_PyRuntimesymbol available in.dynsym_PyGILState_Checksymbol available in.dynsymfor CPython before 3.12
Python Version
There are multiple ways to parse the Python version from an ELF file that we utilize in Perforator.
Py_Version Symbol
There is a Py_Version symbol in the ELF file. This is a global variable that stores 4 bytes of the Python version: the first byte is the major version, the second byte is the minor version, the third byte is the micro version, and the fourth byte is the release level.
Disassemble Py_GetVersion Function
For CPython versions earlier than 3.11, we can disassemble the Py_GetVersion function to get the version.
There is this line of code inside the Py_GetVersion function:
PyOS_snprintf(version, sizeof(version), buildinfo_format, PY_VERSION, Py_GetBuildInfo(), Py_GetCompiler());
Perforator extracts the 4th argument, which is a pointer to a constant global string with the Python version in the .rodata section.
Then, we can read the version as a string from this address in the binary.
_PyRuntime Global Variable
There is a _PyRuntime symbol in the .dynsym section which can be used to obtain the address of the _PyRuntime global variable.
Disassembling PyThreadState_GetCurrent
The PyThreadState_GetCurrent function can be disassembled to get the offset of _PyThreadState_Current in the Thread Local Image for Python Thread State collection.