mirror of
https://github.com/python/cpython.git
synced 2025-11-25 04:34:37 +00:00
gh-91048: Refactor _testexternalinspection and add Windows support (#132852)
This commit is contained in:
parent
f6fb498c97
commit
e8cf3a1a64
8 changed files with 1337 additions and 1481 deletions
|
|
@ -3,8 +3,10 @@ import os
|
|||
import textwrap
|
||||
import importlib
|
||||
import sys
|
||||
import socket
|
||||
from test.support import os_helper, SHORT_TIMEOUT, busy_retry
|
||||
from test.support.script_helper import make_script
|
||||
from test.support.socket_helper import find_unused_port
|
||||
|
||||
import subprocess
|
||||
|
||||
|
|
@ -24,16 +26,24 @@ def _make_test_script(script_dir, script_basename, source):
|
|||
importlib.invalidate_caches()
|
||||
return to_return
|
||||
|
||||
skip_if_not_supported = unittest.skipIf((sys.platform != "darwin"
|
||||
and sys.platform != "linux"
|
||||
and sys.platform != "win32"),
|
||||
"Test only runs on Linux, Windows and MacOS")
|
||||
class TestGetStackTrace(unittest.TestCase):
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_remote_stack_trace(self):
|
||||
# Spawn a process with some realistic Python code
|
||||
script = textwrap.dedent("""\
|
||||
import time, sys
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(f"""\
|
||||
import time, sys, socket
|
||||
# Connect to the test process
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
def bar():
|
||||
for x in range(100):
|
||||
if x == 50:
|
||||
|
|
@ -42,9 +52,7 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
foo()
|
||||
|
||||
def foo():
|
||||
fifo_path = sys.argv[1]
|
||||
with open(fifo_path, "w") as fifo:
|
||||
fifo.write("ready")
|
||||
sock.sendall(b"ready")
|
||||
time.sleep(1000)
|
||||
|
||||
bar()
|
||||
|
|
@ -53,19 +61,28 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
with os_helper.temp_dir() as work_dir:
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
fifo = f"{work_dir}/the_fifo"
|
||||
os.mkfifo(fifo)
|
||||
|
||||
# Create a socket server to communicate with the target process
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(('localhost', port))
|
||||
server_socket.settimeout(SHORT_TIMEOUT)
|
||||
server_socket.listen(1)
|
||||
|
||||
script_name = _make_test_script(script_dir, 'script', script)
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
|
||||
with open(fifo, "r") as fifo_file:
|
||||
response = fifo_file.read()
|
||||
self.assertEqual(response, "ready")
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
response = client_socket.recv(1024)
|
||||
self.assertEqual(response, b"ready")
|
||||
stack_trace = get_stack_trace(p.pid)
|
||||
except PermissionError:
|
||||
self.skipTest("Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
os.remove(fifo)
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.terminate()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
|
@ -79,21 +96,23 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
]
|
||||
self.assertEqual(stack_trace, expected_stack_trace)
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_async_remote_stack_trace(self):
|
||||
# Spawn a process with some realistic Python code
|
||||
script = textwrap.dedent("""\
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(f"""\
|
||||
import asyncio
|
||||
import time
|
||||
import sys
|
||||
import socket
|
||||
# Connect to the test process
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
def c5():
|
||||
fifo_path = sys.argv[1]
|
||||
with open(fifo_path, "w") as fifo:
|
||||
fifo.write("ready")
|
||||
sock.sendall(b"ready")
|
||||
time.sleep(10000)
|
||||
|
||||
async def c4():
|
||||
|
|
@ -122,7 +141,7 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
loop.set_task_factory(eager_task_factory)
|
||||
return loop
|
||||
|
||||
asyncio.run(main(), loop_factory={TASK_FACTORY})
|
||||
asyncio.run(main(), loop_factory={{TASK_FACTORY}})
|
||||
""")
|
||||
stack_trace = None
|
||||
for task_factory_variant in "asyncio.new_event_loop", "new_eager_loop":
|
||||
|
|
@ -132,24 +151,30 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
):
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
fifo = f"{work_dir}/the_fifo"
|
||||
os.mkfifo(fifo)
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(('localhost', port))
|
||||
server_socket.settimeout(SHORT_TIMEOUT)
|
||||
server_socket.listen(1)
|
||||
script_name = _make_test_script(
|
||||
script_dir, 'script',
|
||||
script.format(TASK_FACTORY=task_factory_variant))
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen(
|
||||
[sys.executable, script_name, str(fifo)]
|
||||
[sys.executable, script_name]
|
||||
)
|
||||
with open(fifo, "r") as fifo_file:
|
||||
response = fifo_file.read()
|
||||
self.assertEqual(response, "ready")
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
response = client_socket.recv(1024)
|
||||
self.assertEqual(response, b"ready")
|
||||
stack_trace = get_async_stack_trace(p.pid)
|
||||
except PermissionError:
|
||||
self.skipTest(
|
||||
"Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
os.remove(fifo)
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.terminate()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
|
@ -169,21 +194,23 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
]
|
||||
self.assertEqual(stack_trace, expected_stack_trace)
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_asyncgen_remote_stack_trace(self):
|
||||
# Spawn a process with some realistic Python code
|
||||
script = textwrap.dedent("""\
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(f"""\
|
||||
import asyncio
|
||||
import time
|
||||
import sys
|
||||
import socket
|
||||
# Connect to the test process
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
async def gen_nested_call():
|
||||
fifo_path = sys.argv[1]
|
||||
with open(fifo_path, "w") as fifo:
|
||||
fifo.write("ready")
|
||||
sock.sendall(b"ready")
|
||||
time.sleep(10000)
|
||||
|
||||
async def gen():
|
||||
|
|
@ -202,19 +229,26 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
with os_helper.temp_dir() as work_dir:
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
fifo = f"{work_dir}/the_fifo"
|
||||
os.mkfifo(fifo)
|
||||
# Create a socket server to communicate with the target process
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(('localhost', port))
|
||||
server_socket.settimeout(SHORT_TIMEOUT)
|
||||
server_socket.listen(1)
|
||||
script_name = _make_test_script(script_dir, 'script', script)
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
|
||||
with open(fifo, "r") as fifo_file:
|
||||
response = fifo_file.read()
|
||||
self.assertEqual(response, "ready")
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
response = client_socket.recv(1024)
|
||||
self.assertEqual(response, b"ready")
|
||||
stack_trace = get_async_stack_trace(p.pid)
|
||||
except PermissionError:
|
||||
self.skipTest("Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
os.remove(fifo)
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.terminate()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
|
@ -227,22 +261,24 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
]
|
||||
self.assertEqual(stack_trace, expected_stack_trace)
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_async_gather_remote_stack_trace(self):
|
||||
# Spawn a process with some realistic Python code
|
||||
script = textwrap.dedent("""\
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(f"""\
|
||||
import asyncio
|
||||
import time
|
||||
import sys
|
||||
import socket
|
||||
# Connect to the test process
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
async def deep():
|
||||
await asyncio.sleep(0)
|
||||
fifo_path = sys.argv[1]
|
||||
with open(fifo_path, "w") as fifo:
|
||||
fifo.write("ready")
|
||||
sock.sendall(b"ready")
|
||||
time.sleep(10000)
|
||||
|
||||
async def c1():
|
||||
|
|
@ -261,20 +297,27 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
with os_helper.temp_dir() as work_dir:
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
fifo = f"{work_dir}/the_fifo"
|
||||
os.mkfifo(fifo)
|
||||
# Create a socket server to communicate with the target process
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(('localhost', port))
|
||||
server_socket.settimeout(SHORT_TIMEOUT)
|
||||
server_socket.listen(1)
|
||||
script_name = _make_test_script(script_dir, 'script', script)
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
|
||||
with open(fifo, "r") as fifo_file:
|
||||
response = fifo_file.read()
|
||||
self.assertEqual(response, "ready")
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
response = client_socket.recv(1024)
|
||||
self.assertEqual(response, b"ready")
|
||||
stack_trace = get_async_stack_trace(p.pid)
|
||||
except PermissionError:
|
||||
self.skipTest(
|
||||
"Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
os.remove(fifo)
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.terminate()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
|
@ -287,22 +330,24 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
]
|
||||
self.assertEqual(stack_trace, expected_stack_trace)
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_async_staggered_race_remote_stack_trace(self):
|
||||
# Spawn a process with some realistic Python code
|
||||
script = textwrap.dedent("""\
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(f"""\
|
||||
import asyncio.staggered
|
||||
import time
|
||||
import sys
|
||||
import socket
|
||||
# Connect to the test process
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
async def deep():
|
||||
await asyncio.sleep(0)
|
||||
fifo_path = sys.argv[1]
|
||||
with open(fifo_path, "w") as fifo:
|
||||
fifo.write("ready")
|
||||
sock.sendall(b"ready")
|
||||
time.sleep(10000)
|
||||
|
||||
async def c1():
|
||||
|
|
@ -324,20 +369,27 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
with os_helper.temp_dir() as work_dir:
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
fifo = f"{work_dir}/the_fifo"
|
||||
os.mkfifo(fifo)
|
||||
# Create a socket server to communicate with the target process
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(('localhost', port))
|
||||
server_socket.settimeout(SHORT_TIMEOUT)
|
||||
server_socket.listen(1)
|
||||
script_name = _make_test_script(script_dir, 'script', script)
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
|
||||
with open(fifo, "r") as fifo_file:
|
||||
response = fifo_file.read()
|
||||
self.assertEqual(response, "ready")
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
response = client_socket.recv(1024)
|
||||
self.assertEqual(response, b"ready")
|
||||
stack_trace = get_async_stack_trace(p.pid)
|
||||
except PermissionError:
|
||||
self.skipTest(
|
||||
"Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
os.remove(fifo)
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.terminate()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
|
@ -350,16 +402,17 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
]
|
||||
self.assertEqual(stack_trace, expected_stack_trace)
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_async_global_awaited_by(self):
|
||||
script = textwrap.dedent("""\
|
||||
port = find_unused_port()
|
||||
script = textwrap.dedent(f"""\
|
||||
import asyncio
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import socket
|
||||
from string import ascii_lowercase, digits
|
||||
from test.support import socket_helper, SHORT_TIMEOUT
|
||||
|
||||
|
|
@ -367,6 +420,10 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
PORT = socket_helper.find_unused_port()
|
||||
connections = 0
|
||||
|
||||
# Connect to the test process
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(('localhost', {port}))
|
||||
|
||||
class EchoServerProtocol(asyncio.Protocol):
|
||||
def connection_made(self, transport):
|
||||
global connections
|
||||
|
|
@ -396,9 +453,7 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
tg.create_task(echo_client("".join(msg)))
|
||||
await asyncio.sleep(0)
|
||||
# at least a 1000 tasks created
|
||||
fifo_path = sys.argv[1]
|
||||
with open(fifo_path, "w") as fifo:
|
||||
fifo.write("ready")
|
||||
sock.sendall(b"ready")
|
||||
# at this point all client tasks completed without assertion errors
|
||||
# let's wrap up the test
|
||||
server.close()
|
||||
|
|
@ -418,14 +473,20 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
with os_helper.temp_dir() as work_dir:
|
||||
script_dir = os.path.join(work_dir, "script_pkg")
|
||||
os.mkdir(script_dir)
|
||||
fifo = f"{work_dir}/the_fifo"
|
||||
os.mkfifo(fifo)
|
||||
# Create a socket server to communicate with the target process
|
||||
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
server_socket.bind(('localhost', port))
|
||||
server_socket.settimeout(SHORT_TIMEOUT)
|
||||
server_socket.listen(1)
|
||||
script_name = _make_test_script(script_dir, 'script', script)
|
||||
client_socket = None
|
||||
try:
|
||||
p = subprocess.Popen([sys.executable, script_name, str(fifo)])
|
||||
with open(fifo, "r") as fifo_file:
|
||||
response = fifo_file.read()
|
||||
self.assertEqual(response, "ready")
|
||||
p = subprocess.Popen([sys.executable, script_name])
|
||||
client_socket, _ = server_socket.accept()
|
||||
server_socket.close()
|
||||
response = client_socket.recv(1024)
|
||||
self.assertEqual(response, b"ready")
|
||||
for _ in busy_retry(SHORT_TIMEOUT):
|
||||
try:
|
||||
all_awaited_by = get_all_awaited_by(p.pid)
|
||||
|
|
@ -458,24 +519,32 @@ class TestGetStackTrace(unittest.TestCase):
|
|||
self.assertIn(('Task-1', []), entries)
|
||||
self.assertIn(('server task', [[['main'], 'Task-1', []]]), entries)
|
||||
self.assertIn(('echo client spam', [[['main'], 'Task-1', []]]), entries)
|
||||
|
||||
expected_stack = [[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]]
|
||||
tasks_with_stack = [task for task in entries if task[1] == expected_stack]
|
||||
self.assertGreaterEqual(len(tasks_with_stack), 1000)
|
||||
|
||||
# the final task will have some random number, but it should for
|
||||
# sure be one of the echo client spam horde
|
||||
self.assertEqual([[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]], entries[-1][1])
|
||||
# sure be one of the echo client spam horde (In windows this is not true
|
||||
# for some reason)
|
||||
if sys.platform != "win32":
|
||||
self.assertEqual([[['echo_client_spam'], 'echo client spam', [[['main'], 'Task-1', []]]]], entries[-1][1])
|
||||
except PermissionError:
|
||||
self.skipTest(
|
||||
"Insufficient permissions to read the stack trace")
|
||||
finally:
|
||||
os.remove(fifo)
|
||||
if client_socket is not None:
|
||||
client_socket.close()
|
||||
p.kill()
|
||||
p.terminate()
|
||||
p.wait(timeout=SHORT_TIMEOUT)
|
||||
|
||||
@unittest.skipIf(sys.platform != "darwin" and sys.platform != "linux",
|
||||
"Test only runs on Linux and MacOS")
|
||||
@skip_if_not_supported
|
||||
@unittest.skipIf(sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
|
||||
"Test only runs on Linux with process_vm_readv support")
|
||||
def test_self_trace(self):
|
||||
stack_trace = get_stack_trace(os.getpid())
|
||||
print(stack_trace)
|
||||
self.assertEqual(stack_trace[0], "test_self_trace")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
114
PCbuild/_testexternalinspection.vcxproj
Normal file
114
PCbuild/_testexternalinspection.vcxproj
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|ARM">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|ARM64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|ARM">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|ARM64">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|Win32">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGInstrument|x64">
|
||||
<Configuration>PGInstrument</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|ARM">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|ARM64">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|Win32">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="PGUpdate|x64">
|
||||
<Configuration>PGUpdate</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|ARM64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>ARM64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{4D7C112F-3083-4D9E-9754-9341C14D9B39}</ProjectGuid>
|
||||
<RootNamespace>_testexternalinspection</RootNamespace>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<SupportPGO>false</SupportPGO>
|
||||
</PropertyGroup>
|
||||
<Import Project="python.props" />
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<CharacterSet>NotSet</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<PropertyGroup>
|
||||
<TargetExt>$(PyStdlibPydExt)</TargetExt>
|
||||
</PropertyGroup>
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="pyproject.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Modules\_testexternalinspection.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="..\PC\python_nt.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="pythoncore.vcxproj">
|
||||
<Project>{cf7ac3d1-e2df-41d2-bea6-1e2556cdea26}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="python3dll.vcxproj">
|
||||
<Project>{885d4898-d08d-4091-9c40-c700cfe3fc5a}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
20
PCbuild/_testexternalinspection.vcxproj.filters
Normal file
20
PCbuild/_testexternalinspection.vcxproj.filters
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{6d101329-41df-49a0-8639-f35408ad7c6d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{711941d1-269c-49cb-a733-759b2b91fc61}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\Modules\_testexternalinspection.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="..\PC\python_nt.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
<ExtensionModules Include="@(ExternalModules->'%(Identity)')" Condition="$(IncludeExternals)" />
|
||||
<Projects Include="@(ExtensionModules->'%(Identity).vcxproj')" Condition="$(IncludeExtensions)" />
|
||||
<!-- Test modules -->
|
||||
<TestModules Include="_ctypes_test;_testbuffer;_testcapi;_testlimitedcapi;_testinternalcapi;_testembed;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testclinic;_testclinic_limited" />
|
||||
<TestModules Include="_ctypes_test;_testbuffer;_testcapi;_testlimitedcapi;_testexternalinspection;_testinternalcapi;_testembed;_testimportmultiple;_testmultiphase;_testsinglephase;_testconsole;_testclinic;_testclinic_limited" />
|
||||
<TestModules Include="xxlimited" Condition="'$(Configuration)' == 'Release'" />
|
||||
<TestModules Include="xxlimited_35" Condition="'$(Configuration)' == 'Release'" />
|
||||
<Projects Include="@(TestModules->'%(Identity).vcxproj')" Condition="$(IncludeTests)">
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testclinic", "_testclinic.
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testinternalcapi", "_testinternalcapi.vcxproj", "{900342D7-516A-4469-B1AD-59A66E49A25F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testexternalinspection", "_testexternalinspection.vcxproj", "{4D7C112F-3083-4D9E-9754-9341C14D9B39}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_testimportmultiple", "_testimportmultiple.vcxproj", "{36D0C52C-DF4E-45D0-8BC7-E294C3ABC781}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "_tkinter", "_tkinter.vcxproj", "{4946ECAC-2E69-4BF8-A90A-F5136F5094DF}"
|
||||
|
|
@ -1716,6 +1718,38 @@ Global
|
|||
{FB91C8B2-6FBC-3A01-B644-1637111F902D}.Release|Win32.Build.0 = Release|Win32
|
||||
{FB91C8B2-6FBC-3A01-B644-1637111F902D}.Release|x64.ActiveCfg = Release|x64
|
||||
{FB91C8B2-6FBC-3A01-B644-1637111F902D}.Release|x64.Build.0 = Release|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Debug|x64.Build.0 = Debug|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|ARM.ActiveCfg = PGInstrument|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|ARM.Build.0 = PGInstrument|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|ARM64.ActiveCfg = PGInstrument|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|ARM64.Build.0 = PGInstrument|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|Win32.ActiveCfg = PGInstrument|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|Win32.Build.0 = PGInstrument|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|x64.ActiveCfg = PGInstrument|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGInstrument|x64.Build.0 = PGInstrument|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|ARM.ActiveCfg = PGUpdate|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|ARM.Build.0 = PGUpdate|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|ARM64.ActiveCfg = PGUpdate|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|ARM64.Build.0 = PGUpdate|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|Win32.ActiveCfg = PGUpdate|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|Win32.Build.0 = PGUpdate|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|x64.ActiveCfg = PGUpdate|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.PGUpdate|x64.Build.0 = PGUpdate|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|ARM.Build.0 = Release|ARM
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|Win32.Build.0 = Release|Win32
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|x64.ActiveCfg = Release|x64
|
||||
{4D7C112F-3083-4D9E-9754-9341C14D9B39}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
793
Python/remote_debug.h
Normal file
793
Python/remote_debug.h
Normal file
|
|
@ -0,0 +1,793 @@
|
|||
/*
|
||||
IMPORTANT: This header file is full of static functions that are not exported.
|
||||
|
||||
The reason is that we don't want to export these functions to the Python API
|
||||
and they can be used both for the interpreter and some shared libraries. The
|
||||
reason we don't want to export them is to avoid having them participating in
|
||||
return-oriented programming attacks.
|
||||
|
||||
If you need to add a new function ensure that is declared 'static'.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||
# error "this header requires Py_BUILD_CORE or Py_BUILD_CORE_MODULE define"
|
||||
#endif
|
||||
|
||||
#include "pyconfig.h"
|
||||
#include "internal/pycore_ceval.h"
|
||||
|
||||
#ifdef __linux__
|
||||
# include <elf.h>
|
||||
# include <sys/uio.h>
|
||||
# if INTPTR_MAX == INT64_MAX
|
||||
# define Elf_Ehdr Elf64_Ehdr
|
||||
# define Elf_Shdr Elf64_Shdr
|
||||
# define Elf_Phdr Elf64_Phdr
|
||||
# else
|
||||
# define Elf_Ehdr Elf32_Ehdr
|
||||
# define Elf_Shdr Elf32_Shdr
|
||||
# define Elf_Phdr Elf32_Phdr
|
||||
# endif
|
||||
# include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_OSX
|
||||
# include <libproc.h>
|
||||
# include <mach-o/fat.h>
|
||||
# include <mach-o/loader.h>
|
||||
# include <mach-o/nlist.h>
|
||||
# include <mach/mach.h>
|
||||
# include <mach/mach_vm.h>
|
||||
# include <mach/machine.h>
|
||||
# include <sys/mman.h>
|
||||
# include <sys/proc.h>
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
// Windows includes and definitions
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef MS_WINDOWS
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_PROCESS_VM_READV
|
||||
# define HAVE_PROCESS_VM_READV 0
|
||||
#endif
|
||||
|
||||
// Define a platform-independent process handle structure
|
||||
typedef struct {
|
||||
pid_t pid;
|
||||
#ifdef MS_WINDOWS
|
||||
HANDLE hProcess;
|
||||
#endif
|
||||
} proc_handle_t;
|
||||
|
||||
// Initialize the process handle
|
||||
static int
|
||||
_Py_RemoteDebug_InitProcHandle(proc_handle_t *handle, pid_t pid) {
|
||||
handle->pid = pid;
|
||||
#ifdef MS_WINDOWS
|
||||
handle->hProcess = OpenProcess(
|
||||
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
|
||||
FALSE, pid);
|
||||
if (handle->hProcess == NULL) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Clean up the process handle
|
||||
static void
|
||||
_Py_RemoteDebug_CleanupProcHandle(proc_handle_t *handle) {
|
||||
#ifdef MS_WINDOWS
|
||||
if (handle->hProcess != NULL) {
|
||||
CloseHandle(handle->hProcess);
|
||||
handle->hProcess = NULL;
|
||||
}
|
||||
#endif
|
||||
handle->pid = 0;
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_OSX
|
||||
|
||||
static uintptr_t
|
||||
return_section_address64(
|
||||
const char* section,
|
||||
mach_port_t proc_ref,
|
||||
uintptr_t base,
|
||||
void* map
|
||||
) {
|
||||
struct mach_header_64* hdr = (struct mach_header_64*)map;
|
||||
int ncmds = hdr->ncmds;
|
||||
|
||||
int cmd_cnt = 0;
|
||||
struct segment_command_64* cmd = map + sizeof(struct mach_header_64);
|
||||
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
|
||||
mach_vm_address_t address = (mach_vm_address_t)base;
|
||||
vm_region_basic_info_data_64_t r_info;
|
||||
mach_port_t object_name;
|
||||
uintptr_t vmaddr = 0;
|
||||
|
||||
for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
|
||||
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__TEXT") == 0) {
|
||||
vmaddr = cmd->vmaddr;
|
||||
}
|
||||
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) {
|
||||
while (cmd->filesize != size) {
|
||||
address += size;
|
||||
kern_return_t ret = mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO_64,
|
||||
(vm_region_info_t)&r_info, // cppcheck-suppress [uninitvar]
|
||||
&count,
|
||||
&object_name
|
||||
);
|
||||
if (ret != KERN_SUCCESS) {
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot get any more VM maps.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int nsects = cmd->nsects;
|
||||
struct section_64* sec = (struct section_64*)(
|
||||
(void*)cmd + sizeof(struct segment_command_64)
|
||||
);
|
||||
for (int j = 0; j < nsects; j++) {
|
||||
if (strcmp(sec[j].sectname, section) == 0) {
|
||||
return base + sec[j].addr - vmaddr;
|
||||
}
|
||||
}
|
||||
cmd_cnt++;
|
||||
}
|
||||
|
||||
cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize);
|
||||
}
|
||||
|
||||
// We should not be here, but if we are there, we should say about this
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot find section address.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
return_section_address32(
|
||||
const char* section,
|
||||
mach_port_t proc_ref,
|
||||
uintptr_t base,
|
||||
void* map
|
||||
) {
|
||||
struct mach_header* hdr = (struct mach_header*)map;
|
||||
int ncmds = hdr->ncmds;
|
||||
|
||||
int cmd_cnt = 0;
|
||||
struct segment_command* cmd = map + sizeof(struct mach_header);
|
||||
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_t);
|
||||
mach_vm_address_t address = (mach_vm_address_t)base;
|
||||
vm_region_basic_info_data_t r_info;
|
||||
mach_port_t object_name;
|
||||
uintptr_t vmaddr = 0;
|
||||
|
||||
for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
|
||||
if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__TEXT") == 0) {
|
||||
vmaddr = cmd->vmaddr;
|
||||
}
|
||||
if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__DATA") == 0) {
|
||||
while (cmd->filesize != size) {
|
||||
address += size;
|
||||
kern_return_t ret = mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&r_info, // cppcheck-suppress [uninitvar]
|
||||
&count,
|
||||
&object_name
|
||||
);
|
||||
if (ret != KERN_SUCCESS) {
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot get any more VM maps.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int nsects = cmd->nsects;
|
||||
struct section* sec = (struct section*)(
|
||||
(void*)cmd + sizeof(struct segment_command)
|
||||
);
|
||||
for (int j = 0; j < nsects; j++) {
|
||||
if (strcmp(sec[j].sectname, section) == 0) {
|
||||
return base + sec[j].addr - vmaddr;
|
||||
}
|
||||
}
|
||||
cmd_cnt++;
|
||||
}
|
||||
|
||||
cmd = (struct segment_command*)((void*)cmd + cmd->cmdsize);
|
||||
}
|
||||
|
||||
// We should not be here, but if we are there, we should say about this
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot find section address.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
return_section_address_fat(
|
||||
const char* section,
|
||||
mach_port_t proc_ref,
|
||||
uintptr_t base,
|
||||
void* map
|
||||
) {
|
||||
struct fat_header* fat_hdr = (struct fat_header*)map;
|
||||
|
||||
// Determine host CPU type for architecture selection
|
||||
cpu_type_t cpu;
|
||||
int is_abi64;
|
||||
size_t cpu_size = sizeof(cpu), abi64_size = sizeof(is_abi64);
|
||||
|
||||
sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0);
|
||||
sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0);
|
||||
|
||||
cpu |= is_abi64 * CPU_ARCH_ABI64;
|
||||
|
||||
// Check endianness
|
||||
int swap = fat_hdr->magic == FAT_CIGAM;
|
||||
struct fat_arch* arch = (struct fat_arch*)(map + sizeof(struct fat_header));
|
||||
|
||||
// Get number of architectures in fat binary
|
||||
uint32_t nfat_arch = swap ? __builtin_bswap32(fat_hdr->nfat_arch) : fat_hdr->nfat_arch;
|
||||
|
||||
// Search for matching architecture
|
||||
for (uint32_t i = 0; i < nfat_arch; i++) {
|
||||
cpu_type_t arch_cpu = swap ? __builtin_bswap32(arch[i].cputype) : arch[i].cputype;
|
||||
|
||||
if (arch_cpu == cpu) {
|
||||
// Found matching architecture, now process it
|
||||
uint32_t offset = swap ? __builtin_bswap32(arch[i].offset) : arch[i].offset;
|
||||
struct mach_header_64* hdr = (struct mach_header_64*)(map + offset);
|
||||
|
||||
// Determine which type of Mach-O it is and process accordingly
|
||||
switch (hdr->magic) {
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
return return_section_address32(section, proc_ref, base, (void*)hdr);
|
||||
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
return return_section_address64(section, proc_ref, base, (void*)hdr);
|
||||
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic in fat binary.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_RuntimeError, "No matching architecture found in fat binary.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_size_t size, mach_port_t proc_ref)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot open binary %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat fs;
|
||||
if (fstat(fd, &fs) == -1) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot get size of binary %s\n", path);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (map == MAP_FAILED) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot map binary %s\n", path);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t result = 0;
|
||||
uint32_t magic = *(uint32_t*)map;
|
||||
|
||||
switch (magic) {
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
result = return_section_address32(secname, proc_ref, base, map);
|
||||
break;
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
result = return_section_address64(secname, proc_ref, base, map);
|
||||
break;
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
result = return_section_address_fat(secname, proc_ref, base, map);
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic");
|
||||
break;
|
||||
}
|
||||
|
||||
munmap(map, fs.st_size);
|
||||
if (close(fd) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
static mach_port_t
|
||||
pid_to_task(pid_t pid)
|
||||
{
|
||||
mach_port_t task;
|
||||
kern_return_t result;
|
||||
|
||||
result = task_for_pid(mach_task_self(), pid, &task);
|
||||
if (result != KERN_SUCCESS) {
|
||||
PyErr_Format(PyExc_PermissionError, "Cannot get task for PID %d", pid);
|
||||
return 0;
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr) {
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
|
||||
vm_region_basic_info_data_64_t region_info;
|
||||
mach_port_t object_name;
|
||||
|
||||
mach_port_t proc_ref = pid_to_task(handle->pid);
|
||||
if (proc_ref == 0) {
|
||||
PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int match_found = 0;
|
||||
char map_filename[MAXPATHLEN + 1];
|
||||
while (mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO_64,
|
||||
(vm_region_info_t)®ion_info,
|
||||
&count,
|
||||
&object_name) == KERN_SUCCESS)
|
||||
{
|
||||
if ((region_info.protection & VM_PROT_READ) == 0
|
||||
|| (region_info.protection & VM_PROT_EXECUTE) == 0) {
|
||||
address += size;
|
||||
continue;
|
||||
}
|
||||
|
||||
int path_len = proc_regionfilename(
|
||||
handle->pid, address, map_filename, MAXPATHLEN);
|
||||
if (path_len == 0) {
|
||||
address += size;
|
||||
continue;
|
||||
}
|
||||
|
||||
char* filename = strrchr(map_filename, '/');
|
||||
if (filename != NULL) {
|
||||
filename++; // Move past the '/'
|
||||
} else {
|
||||
filename = map_filename; // No path, use the whole string
|
||||
}
|
||||
|
||||
if (!match_found && strncmp(filename, substr, strlen(substr)) == 0) {
|
||||
match_found = 1;
|
||||
return search_section_in_file(
|
||||
secname, map_filename, address, size, proc_ref);
|
||||
}
|
||||
|
||||
address += size;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"mach_vm_region failed to find the section");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // (__APPLE__ && TARGET_OS_OSX)
|
||||
|
||||
#if defined(__linux__) && HAVE_PROCESS_VM_READV
|
||||
static uintptr_t
|
||||
search_elf_file_for_section(
|
||||
proc_handle_t *handle,
|
||||
const char* secname,
|
||||
uintptr_t start_address,
|
||||
const char *elf_file)
|
||||
{
|
||||
if (start_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t result = 0;
|
||||
void* file_memory = NULL;
|
||||
|
||||
int fd = open(elf_file, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
struct stat file_stats;
|
||||
if (fstat(fd, &file_stats) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (file_memory == MAP_FAILED) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory;
|
||||
|
||||
Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff);
|
||||
|
||||
Elf_Shdr* shstrtab_section = §ion_header_table[elf_header->e_shstrndx];
|
||||
char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset);
|
||||
|
||||
Elf_Shdr* section = NULL;
|
||||
for (int i = 0; i < elf_header->e_shnum; i++) {
|
||||
char* this_sec_name = shstrtab + section_header_table[i].sh_name;
|
||||
// Move 1 character to account for the leading "."
|
||||
this_sec_name += 1;
|
||||
if (strcmp(secname, this_sec_name) == 0) {
|
||||
section = §ion_header_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff);
|
||||
// Find the first PT_LOAD segment
|
||||
Elf_Phdr* first_load_segment = NULL;
|
||||
for (int i = 0; i < elf_header->e_phnum; i++) {
|
||||
if (program_header_table[i].p_type == PT_LOAD) {
|
||||
first_load_segment = &program_header_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (section != NULL && first_load_segment != NULL) {
|
||||
uintptr_t elf_load_addr = first_load_segment->p_vaddr
|
||||
- (first_load_segment->p_vaddr % first_load_segment->p_align);
|
||||
result = start_address + (uintptr_t)section->sh_addr - elf_load_addr;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (file_memory != NULL) {
|
||||
munmap(file_memory, file_stats.st_size);
|
||||
}
|
||||
if (fd >= 0 && close(fd) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr)
|
||||
{
|
||||
char maps_file_path[64];
|
||||
sprintf(maps_file_path, "/proc/%d/maps", handle->pid);
|
||||
|
||||
FILE* maps_file = fopen(maps_file_path, "r");
|
||||
if (maps_file == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t linelen = 0;
|
||||
size_t linesz = PATH_MAX;
|
||||
char *line = PyMem_Malloc(linesz);
|
||||
if (!line) {
|
||||
fclose(maps_file);
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t retval = 0;
|
||||
while (fgets(line + linelen, linesz - linelen, maps_file) != NULL) {
|
||||
linelen = strlen(line);
|
||||
if (line[linelen - 1] != '\n') {
|
||||
// Read a partial line: realloc and keep reading where we left off.
|
||||
// Note that even the last line will be terminated by a newline.
|
||||
linesz *= 2;
|
||||
char *biggerline = PyMem_Realloc(line, linesz);
|
||||
if (!biggerline) {
|
||||
PyMem_Free(line);
|
||||
fclose(maps_file);
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
line = biggerline;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read a full line: strip the newline
|
||||
line[linelen - 1] = '\0';
|
||||
// and prepare to read the next line into the start of the buffer.
|
||||
linelen = 0;
|
||||
|
||||
unsigned long start = 0;
|
||||
unsigned long path_pos = 0;
|
||||
sscanf(line, "%lx-%*x %*s %*s %*s %*s %ln", &start, &path_pos);
|
||||
|
||||
if (!path_pos) {
|
||||
// Line didn't match our format string. This shouldn't be
|
||||
// possible, but let's be defensive and skip the line.
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *path = line + path_pos;
|
||||
const char *filename = strrchr(path, '/');
|
||||
if (filename) {
|
||||
filename++; // Move past the '/'
|
||||
} else {
|
||||
filename = path; // No directories, or an empty string
|
||||
}
|
||||
|
||||
if (strstr(filename, substr)) {
|
||||
retval = search_elf_file_for_section(handle, secname, start, path);
|
||||
if (retval) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyMem_Free(line);
|
||||
fclose(maps_file);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
|
||||
static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char* secname) {
|
||||
HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
return NULL;
|
||||
}
|
||||
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0);
|
||||
if (!hMap) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
|
||||
if (!mapView) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)mapView;
|
||||
if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid DOS signature.");
|
||||
UnmapViewOfFile(mapView);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)(mapView + pDOSHeader->e_lfanew);
|
||||
if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid NT signature.");
|
||||
UnmapViewOfFile(mapView);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_SECTION_HEADER* pSection_header = (IMAGE_SECTION_HEADER*)(mapView + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
|
||||
void* runtime_addr = NULL;
|
||||
|
||||
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
|
||||
const char* name = (const char*)pSection_header[i].Name;
|
||||
if (strncmp(name, secname, IMAGE_SIZEOF_SHORT_NAME) == 0) {
|
||||
runtime_addr = remote_base + pSection_header[i].VirtualAddress;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UnmapViewOfFile(mapView);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
|
||||
return runtime_addr;
|
||||
}
|
||||
|
||||
|
||||
static uintptr_t
|
||||
search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr) {
|
||||
HANDLE hProcSnap;
|
||||
do {
|
||||
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid);
|
||||
} while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
|
||||
|
||||
if (hProcSnap == INVALID_HANDLE_VALUE) {
|
||||
PyErr_SetString(PyExc_PermissionError, "Unable to create module snapshot. Check permissions or PID.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULEENTRY32W moduleEntry;
|
||||
moduleEntry.dwSize = sizeof(moduleEntry);
|
||||
void* runtime_addr = NULL;
|
||||
|
||||
for (BOOL hasModule = Module32FirstW(hProcSnap, &moduleEntry); hasModule; hasModule = Module32NextW(hProcSnap, &moduleEntry)) {
|
||||
// Look for either python executable or DLL
|
||||
if (wcsstr(moduleEntry.szModule, substr)) {
|
||||
runtime_addr = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname);
|
||||
if (runtime_addr != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hProcSnap);
|
||||
return (uintptr_t)runtime_addr;
|
||||
}
|
||||
|
||||
#endif // MS_WINDOWS
|
||||
|
||||
// Get the PyRuntime section address for any platform
|
||||
static uintptr_t
|
||||
_Py_RemoteDebug_GetPyRuntimeAddress(proc_handle_t* handle)
|
||||
{
|
||||
uintptr_t address = 0;
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
// On Windows, search for 'python' in executable or DLL
|
||||
address = search_windows_map_for_section(handle, "PyRuntime", L"python");
|
||||
if (address == 0) {
|
||||
// Error out: 'python' substring covers both executable and DLL
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
// On Linux, search for 'python' in executable or DLL
|
||||
address = search_linux_map_for_section(handle, "PyRuntime", "python");
|
||||
if (address == 0) {
|
||||
// Error out: 'python' substring covers both executable and DLL
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
|
||||
}
|
||||
#else
|
||||
// On macOS, try libpython first, then fall back to python
|
||||
address = search_map_for_section(handle, "PyRuntime", "libpython");
|
||||
if (address == 0) {
|
||||
// TODO: Differentiate between not found and error
|
||||
PyErr_Clear();
|
||||
address = search_map_for_section(handle, "PyRuntime", "python");
|
||||
}
|
||||
#endif
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
// Platform-independent memory read function
|
||||
static int
|
||||
_Py_RemoteDebug_ReadRemoteMemory(proc_handle_t *handle, uintptr_t remote_address, size_t len, void* dst)
|
||||
{
|
||||
#ifdef MS_WINDOWS
|
||||
SIZE_T read_bytes = 0;
|
||||
SIZE_T result = 0;
|
||||
do {
|
||||
if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address + result), (char*)dst + result, len - result, &read_bytes)) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
return -1;
|
||||
}
|
||||
result += read_bytes;
|
||||
} while (result < len);
|
||||
return 0;
|
||||
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
|
||||
struct iovec local[1];
|
||||
struct iovec remote[1];
|
||||
Py_ssize_t result = 0;
|
||||
Py_ssize_t read_bytes = 0;
|
||||
|
||||
do {
|
||||
local[0].iov_base = (char*)dst + result;
|
||||
local[0].iov_len = len - result;
|
||||
remote[0].iov_base = (void*)(remote_address + result);
|
||||
remote[0].iov_len = len - result;
|
||||
|
||||
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
|
||||
if (read_bytes < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result += read_bytes;
|
||||
} while ((size_t)read_bytes != local[0].iov_len);
|
||||
return 0;
|
||||
#elif defined(__APPLE__) && TARGET_OS_OSX
|
||||
Py_ssize_t result = -1;
|
||||
kern_return_t kr = mach_vm_read_overwrite(
|
||||
pid_to_task(handle->pid),
|
||||
(mach_vm_address_t)remote_address,
|
||||
len,
|
||||
(mach_vm_address_t)dst,
|
||||
(mach_vm_size_t*)&result);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
switch (kr) {
|
||||
case KERN_PROTECTION_FAILURE:
|
||||
PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory");
|
||||
break;
|
||||
case KERN_INVALID_ARGUMENT:
|
||||
PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite");
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
Py_UNREACHABLE();
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
_Py_RemoteDebug_ReadDebugOffsets(
|
||||
proc_handle_t *handle,
|
||||
uintptr_t *runtime_start_address,
|
||||
_Py_DebugOffsets* debug_offsets
|
||||
) {
|
||||
*runtime_start_address = _Py_RemoteDebug_GetPyRuntimeAddress(handle);
|
||||
if (!*runtime_start_address) {
|
||||
if (!PyErr_Occurred()) {
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Failed to get PyRuntime address");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
size_t size = sizeof(struct _Py_DebugOffsets);
|
||||
if (0 != _Py_RemoteDebug_ReadRemoteMemory(handle, *runtime_start_address, size, debug_offsets)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -5,754 +5,25 @@
|
|||
#include "internal/pycore_runtime.h"
|
||||
#include "internal/pycore_ceval.h"
|
||||
|
||||
#ifdef __linux__
|
||||
# include <elf.h>
|
||||
# include <sys/uio.h>
|
||||
# if INTPTR_MAX == INT64_MAX
|
||||
# define Elf_Ehdr Elf64_Ehdr
|
||||
# define Elf_Shdr Elf64_Shdr
|
||||
# define Elf_Phdr Elf64_Phdr
|
||||
# else
|
||||
# define Elf_Ehdr Elf32_Ehdr
|
||||
# define Elf_Shdr Elf32_Shdr
|
||||
# define Elf_Phdr Elf32_Phdr
|
||||
# endif
|
||||
# include <sys/mman.h>
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_OSX
|
||||
# include <libproc.h>
|
||||
# include <mach-o/fat.h>
|
||||
# include <mach-o/loader.h>
|
||||
# include <mach-o/nlist.h>
|
||||
# include <mach/mach.h>
|
||||
# include <mach/mach_vm.h>
|
||||
# include <mach/machine.h>
|
||||
# include <sys/mman.h>
|
||||
# include <sys/proc.h>
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
// Windows includes and definitions
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifndef MS_WINDOWS
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_PROCESS_VM_READV
|
||||
# define HAVE_PROCESS_VM_READV 0
|
||||
#endif
|
||||
|
||||
#if defined(Py_REMOTE_DEBUG) && defined(Py_SUPPORTS_REMOTE_DEBUG)
|
||||
#include "remote_debug.h"
|
||||
|
||||
// Define a platform-independent process handle structure
|
||||
typedef struct {
|
||||
pid_t pid;
|
||||
#ifdef MS_WINDOWS
|
||||
HANDLE hProcess;
|
||||
#endif
|
||||
} proc_handle_t;
|
||||
|
||||
// Initialize the process handle
|
||||
static int
|
||||
init_proc_handle(proc_handle_t *handle, pid_t pid) {
|
||||
handle->pid = pid;
|
||||
#ifdef MS_WINDOWS
|
||||
handle->hProcess = OpenProcess(
|
||||
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION,
|
||||
FALSE, pid);
|
||||
if (handle->hProcess == NULL) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
return _Py_RemoteDebug_InitProcHandle(handle, pid);
|
||||
}
|
||||
|
||||
// Clean up the process handle
|
||||
static void
|
||||
cleanup_proc_handle(proc_handle_t *handle) {
|
||||
#ifdef MS_WINDOWS
|
||||
if (handle->hProcess != NULL) {
|
||||
CloseHandle(handle->hProcess);
|
||||
handle->hProcess = NULL;
|
||||
}
|
||||
#endif
|
||||
handle->pid = 0;
|
||||
_Py_RemoteDebug_CleanupProcHandle(handle);
|
||||
}
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_OSX
|
||||
static uintptr_t
|
||||
return_section_address64(
|
||||
const char* section,
|
||||
mach_port_t proc_ref,
|
||||
uintptr_t base,
|
||||
void* map
|
||||
) {
|
||||
struct mach_header_64* hdr = (struct mach_header_64*)map;
|
||||
int ncmds = hdr->ncmds;
|
||||
|
||||
int cmd_cnt = 0;
|
||||
struct segment_command_64* cmd = map + sizeof(struct mach_header_64);
|
||||
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
|
||||
mach_vm_address_t address = (mach_vm_address_t)base;
|
||||
vm_region_basic_info_data_64_t r_info;
|
||||
mach_port_t object_name;
|
||||
uintptr_t vmaddr = 0;
|
||||
|
||||
for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
|
||||
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__TEXT") == 0) {
|
||||
vmaddr = cmd->vmaddr;
|
||||
}
|
||||
if (cmd->cmd == LC_SEGMENT_64 && strcmp(cmd->segname, "__DATA") == 0) {
|
||||
while (cmd->filesize != size) {
|
||||
address += size;
|
||||
kern_return_t ret = mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO_64,
|
||||
(vm_region_info_t)&r_info, // cppcheck-suppress [uninitvar]
|
||||
&count,
|
||||
&object_name
|
||||
);
|
||||
if (ret != KERN_SUCCESS) {
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot get any more VM maps.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int nsects = cmd->nsects;
|
||||
struct section_64* sec = (struct section_64*)(
|
||||
(void*)cmd + sizeof(struct segment_command_64)
|
||||
);
|
||||
for (int j = 0; j < nsects; j++) {
|
||||
if (strcmp(sec[j].sectname, section) == 0) {
|
||||
return base + sec[j].addr - vmaddr;
|
||||
}
|
||||
}
|
||||
cmd_cnt++;
|
||||
}
|
||||
|
||||
cmd = (struct segment_command_64*)((void*)cmd + cmd->cmdsize);
|
||||
}
|
||||
|
||||
// We should not be here, but if we are there, we should say about this
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot find section address.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
return_section_address32(
|
||||
const char* section,
|
||||
mach_port_t proc_ref,
|
||||
uintptr_t base,
|
||||
void* map
|
||||
) {
|
||||
struct mach_header* hdr = (struct mach_header*)map;
|
||||
int ncmds = hdr->ncmds;
|
||||
|
||||
int cmd_cnt = 0;
|
||||
struct segment_command* cmd = map + sizeof(struct mach_header);
|
||||
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_t);
|
||||
mach_vm_address_t address = (mach_vm_address_t)base;
|
||||
vm_region_basic_info_data_t r_info;
|
||||
mach_port_t object_name;
|
||||
uintptr_t vmaddr = 0;
|
||||
|
||||
for (int i = 0; cmd_cnt < 2 && i < ncmds; i++) {
|
||||
if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__TEXT") == 0) {
|
||||
vmaddr = cmd->vmaddr;
|
||||
}
|
||||
if (cmd->cmd == LC_SEGMENT && strcmp(cmd->segname, "__DATA") == 0) {
|
||||
while (cmd->filesize != size) {
|
||||
address += size;
|
||||
kern_return_t ret = mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&r_info, // cppcheck-suppress [uninitvar]
|
||||
&count,
|
||||
&object_name
|
||||
);
|
||||
if (ret != KERN_SUCCESS) {
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot get any more VM maps.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int nsects = cmd->nsects;
|
||||
struct section* sec = (struct section*)(
|
||||
(void*)cmd + sizeof(struct segment_command)
|
||||
);
|
||||
for (int j = 0; j < nsects; j++) {
|
||||
if (strcmp(sec[j].sectname, section) == 0) {
|
||||
return base + sec[j].addr - vmaddr;
|
||||
}
|
||||
}
|
||||
cmd_cnt++;
|
||||
}
|
||||
|
||||
cmd = (struct segment_command*)((void*)cmd + cmd->cmdsize);
|
||||
}
|
||||
|
||||
// We should not be here, but if we are there, we should say about this
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Cannot find section address.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
return_section_address_fat(
|
||||
const char* section,
|
||||
mach_port_t proc_ref,
|
||||
uintptr_t base,
|
||||
void* map
|
||||
) {
|
||||
struct fat_header* fat_hdr = (struct fat_header*)map;
|
||||
|
||||
// Determine host CPU type for architecture selection
|
||||
cpu_type_t cpu;
|
||||
int is_abi64;
|
||||
size_t cpu_size = sizeof(cpu), abi64_size = sizeof(is_abi64);
|
||||
|
||||
sysctlbyname("hw.cputype", &cpu, &cpu_size, NULL, 0);
|
||||
sysctlbyname("hw.cpu64bit_capable", &is_abi64, &abi64_size, NULL, 0);
|
||||
|
||||
cpu |= is_abi64 * CPU_ARCH_ABI64;
|
||||
|
||||
// Check endianness
|
||||
int swap = fat_hdr->magic == FAT_CIGAM;
|
||||
struct fat_arch* arch = (struct fat_arch*)(map + sizeof(struct fat_header));
|
||||
|
||||
// Get number of architectures in fat binary
|
||||
uint32_t nfat_arch = swap ? __builtin_bswap32(fat_hdr->nfat_arch) : fat_hdr->nfat_arch;
|
||||
|
||||
// Search for matching architecture
|
||||
for (uint32_t i = 0; i < nfat_arch; i++) {
|
||||
cpu_type_t arch_cpu = swap ? __builtin_bswap32(arch[i].cputype) : arch[i].cputype;
|
||||
|
||||
if (arch_cpu == cpu) {
|
||||
// Found matching architecture, now process it
|
||||
uint32_t offset = swap ? __builtin_bswap32(arch[i].offset) : arch[i].offset;
|
||||
struct mach_header_64* hdr = (struct mach_header_64*)(map + offset);
|
||||
|
||||
// Determine which type of Mach-O it is and process accordingly
|
||||
switch (hdr->magic) {
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
return return_section_address32(section, proc_ref, base, (void*)hdr);
|
||||
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
return return_section_address64(section, proc_ref, base, (void*)hdr);
|
||||
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic in fat binary.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_RuntimeError, "No matching architecture found in fat binary.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
search_section_in_file(const char* secname, char* path, uintptr_t base, mach_vm_size_t size, mach_port_t proc_ref)
|
||||
{
|
||||
int fd = open(path, O_RDONLY);
|
||||
if (fd == -1) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot open binary %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct stat fs;
|
||||
if (fstat(fd, &fs) == -1) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot get size of binary %s\n", path);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* map = mmap(0, fs.st_size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
if (map == MAP_FAILED) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Cannot map binary %s\n", path);
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t result = 0;
|
||||
uint32_t magic = *(uint32_t*)map;
|
||||
|
||||
switch (magic) {
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
result = return_section_address32(secname, proc_ref, base, map);
|
||||
break;
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
result = return_section_address64(secname, proc_ref, base, map);
|
||||
break;
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
result = return_section_address_fat(secname, proc_ref, base, map);
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown Mach-O magic");
|
||||
break;
|
||||
}
|
||||
|
||||
munmap(map, fs.st_size);
|
||||
if (close(fd) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static mach_port_t
|
||||
pid_to_task(pid_t pid)
|
||||
{
|
||||
mach_port_t task;
|
||||
kern_return_t result;
|
||||
|
||||
result = task_for_pid(mach_task_self(), pid, &task);
|
||||
if (result != KERN_SUCCESS) {
|
||||
PyErr_Format(PyExc_PermissionError, "Cannot get task for PID %d", pid);
|
||||
return 0;
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
search_map_for_section(proc_handle_t *handle, const char* secname, const char* substr) {
|
||||
mach_vm_address_t address = 0;
|
||||
mach_vm_size_t size = 0;
|
||||
mach_msg_type_number_t count = sizeof(vm_region_basic_info_data_64_t);
|
||||
vm_region_basic_info_data_64_t region_info;
|
||||
mach_port_t object_name;
|
||||
|
||||
mach_port_t proc_ref = pid_to_task(handle->pid);
|
||||
if (proc_ref == 0) {
|
||||
PyErr_SetString(PyExc_PermissionError, "Cannot get task for PID");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int match_found = 0;
|
||||
char map_filename[MAXPATHLEN + 1];
|
||||
while (mach_vm_region(
|
||||
proc_ref,
|
||||
&address,
|
||||
&size,
|
||||
VM_REGION_BASIC_INFO_64,
|
||||
(vm_region_info_t)®ion_info,
|
||||
&count,
|
||||
&object_name) == KERN_SUCCESS)
|
||||
{
|
||||
if ((region_info.protection & VM_PROT_READ) == 0
|
||||
|| (region_info.protection & VM_PROT_EXECUTE) == 0) {
|
||||
address += size;
|
||||
continue;
|
||||
}
|
||||
|
||||
int path_len = proc_regionfilename(
|
||||
handle->pid, address, map_filename, MAXPATHLEN);
|
||||
if (path_len == 0) {
|
||||
address += size;
|
||||
continue;
|
||||
}
|
||||
|
||||
char* filename = strrchr(map_filename, '/');
|
||||
if (filename != NULL) {
|
||||
filename++; // Move past the '/'
|
||||
} else {
|
||||
filename = map_filename; // No path, use the whole string
|
||||
}
|
||||
|
||||
if (!match_found && strncmp(filename, substr, strlen(substr)) == 0) {
|
||||
match_found = 1;
|
||||
return search_section_in_file(
|
||||
secname, map_filename, address, size, proc_ref);
|
||||
}
|
||||
|
||||
address += size;
|
||||
}
|
||||
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"mach_vm_region failed to find the section");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // (__APPLE__ && TARGET_OS_OSX)
|
||||
|
||||
#if defined(__linux__) && HAVE_PROCESS_VM_READV
|
||||
static uintptr_t
|
||||
search_elf_file_for_section(
|
||||
proc_handle_t *handle,
|
||||
const char* secname,
|
||||
uintptr_t start_address,
|
||||
const char *elf_file)
|
||||
{
|
||||
if (start_address == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t result = 0;
|
||||
void* file_memory = NULL;
|
||||
|
||||
int fd = open(elf_file, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
struct stat file_stats;
|
||||
if (fstat(fd, &file_stats) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
file_memory = mmap(NULL, file_stats.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (file_memory == MAP_FAILED) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
Elf_Ehdr* elf_header = (Elf_Ehdr*)file_memory;
|
||||
|
||||
Elf_Shdr* section_header_table = (Elf_Shdr*)(file_memory + elf_header->e_shoff);
|
||||
|
||||
Elf_Shdr* shstrtab_section = §ion_header_table[elf_header->e_shstrndx];
|
||||
char* shstrtab = (char*)(file_memory + shstrtab_section->sh_offset);
|
||||
|
||||
Elf_Shdr* section = NULL;
|
||||
for (int i = 0; i < elf_header->e_shnum; i++) {
|
||||
char* this_sec_name = shstrtab + section_header_table[i].sh_name;
|
||||
// Move 1 character to account for the leading "."
|
||||
this_sec_name += 1;
|
||||
if (strcmp(secname, this_sec_name) == 0) {
|
||||
section = §ion_header_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Elf_Phdr* program_header_table = (Elf_Phdr*)(file_memory + elf_header->e_phoff);
|
||||
// Find the first PT_LOAD segment
|
||||
Elf_Phdr* first_load_segment = NULL;
|
||||
for (int i = 0; i < elf_header->e_phnum; i++) {
|
||||
if (program_header_table[i].p_type == PT_LOAD) {
|
||||
first_load_segment = &program_header_table[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (section != NULL && first_load_segment != NULL) {
|
||||
uintptr_t elf_load_addr = first_load_segment->p_vaddr
|
||||
- (first_load_segment->p_vaddr % first_load_segment->p_align);
|
||||
result = start_address + (uintptr_t)section->sh_addr - elf_load_addr;
|
||||
}
|
||||
|
||||
exit:
|
||||
if (file_memory != NULL) {
|
||||
munmap(file_memory, file_stats.st_size);
|
||||
}
|
||||
if (fd >= 0 && close(fd) != 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static uintptr_t
|
||||
search_linux_map_for_section(proc_handle_t *handle, const char* secname, const char* substr)
|
||||
{
|
||||
char maps_file_path[64];
|
||||
sprintf(maps_file_path, "/proc/%d/maps", handle->pid);
|
||||
|
||||
FILE* maps_file = fopen(maps_file_path, "r");
|
||||
if (maps_file == NULL) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t linelen = 0;
|
||||
size_t linesz = PATH_MAX;
|
||||
char *line = PyMem_Malloc(linesz);
|
||||
if (!line) {
|
||||
fclose(maps_file);
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t retval = 0;
|
||||
while (fgets(line + linelen, linesz - linelen, maps_file) != NULL) {
|
||||
linelen = strlen(line);
|
||||
if (line[linelen - 1] != '\n') {
|
||||
// Read a partial line: realloc and keep reading where we left off.
|
||||
// Note that even the last line will be terminated by a newline.
|
||||
linesz *= 2;
|
||||
char *biggerline = PyMem_Realloc(line, linesz);
|
||||
if (!biggerline) {
|
||||
PyMem_Free(line);
|
||||
fclose(maps_file);
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
line = biggerline;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read a full line: strip the newline
|
||||
line[linelen - 1] = '\0';
|
||||
// and prepare to read the next line into the start of the buffer.
|
||||
linelen = 0;
|
||||
|
||||
unsigned long start = 0;
|
||||
unsigned long path_pos = 0;
|
||||
sscanf(line, "%lx-%*x %*s %*s %*s %*s %ln", &start, &path_pos);
|
||||
|
||||
if (!path_pos) {
|
||||
// Line didn't match our format string. This shouldn't be
|
||||
// possible, but let's be defensive and skip the line.
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *path = line + path_pos;
|
||||
const char *filename = strrchr(path, '/');
|
||||
if (filename) {
|
||||
filename++; // Move past the '/'
|
||||
} else {
|
||||
filename = path; // No directories, or an empty string
|
||||
}
|
||||
|
||||
if (strstr(filename, substr)) {
|
||||
retval = search_elf_file_for_section(handle, secname, start, path);
|
||||
if (retval) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyMem_Free(line);
|
||||
fclose(maps_file);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
#endif // __linux__
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
|
||||
static void* analyze_pe(const wchar_t* mod_path, BYTE* remote_base, const char* secname) {
|
||||
HANDLE hFile = CreateFileW(mod_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
return NULL;
|
||||
}
|
||||
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0);
|
||||
if (!hMap) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BYTE* mapView = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
|
||||
if (!mapView) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_DOS_HEADER* pDOSHeader = (IMAGE_DOS_HEADER*)mapView;
|
||||
if (pDOSHeader->e_magic != IMAGE_DOS_SIGNATURE) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid DOS signature.");
|
||||
UnmapViewOfFile(mapView);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_NT_HEADERS* pNTHeaders = (IMAGE_NT_HEADERS*)(mapView + pDOSHeader->e_lfanew);
|
||||
if (pNTHeaders->Signature != IMAGE_NT_SIGNATURE) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Invalid NT signature.");
|
||||
UnmapViewOfFile(mapView);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IMAGE_SECTION_HEADER* pSection_header = (IMAGE_SECTION_HEADER*)(mapView + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
|
||||
void* runtime_addr = NULL;
|
||||
|
||||
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
|
||||
const char* name = (const char*)pSection_header[i].Name;
|
||||
if (strncmp(name, secname, IMAGE_SIZEOF_SHORT_NAME) == 0) {
|
||||
runtime_addr = remote_base + pSection_header[i].VirtualAddress;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
UnmapViewOfFile(mapView);
|
||||
CloseHandle(hMap);
|
||||
CloseHandle(hFile);
|
||||
|
||||
return runtime_addr;
|
||||
}
|
||||
|
||||
|
||||
static uintptr_t
|
||||
search_windows_map_for_section(proc_handle_t* handle, const char* secname, const wchar_t* substr) {
|
||||
HANDLE hProcSnap;
|
||||
do {
|
||||
hProcSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, handle->pid);
|
||||
} while (hProcSnap == INVALID_HANDLE_VALUE && GetLastError() == ERROR_BAD_LENGTH);
|
||||
|
||||
if (hProcSnap == INVALID_HANDLE_VALUE) {
|
||||
PyErr_SetString(PyExc_PermissionError, "Unable to create module snapshot. Check permissions or PID.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
MODULEENTRY32W moduleEntry;
|
||||
moduleEntry.dwSize = sizeof(moduleEntry);
|
||||
void* runtime_addr = NULL;
|
||||
|
||||
for (BOOL hasModule = Module32FirstW(hProcSnap, &moduleEntry); hasModule; hasModule = Module32NextW(hProcSnap, &moduleEntry)) {
|
||||
// Look for either python executable or DLL
|
||||
if (wcsstr(moduleEntry.szModule, substr)) {
|
||||
runtime_addr = analyze_pe(moduleEntry.szExePath, moduleEntry.modBaseAddr, secname);
|
||||
if (runtime_addr != NULL) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hProcSnap);
|
||||
return (uintptr_t)runtime_addr;
|
||||
}
|
||||
|
||||
#endif // MS_WINDOWS
|
||||
|
||||
// Get the PyRuntime section address for any platform
|
||||
static uintptr_t
|
||||
get_py_runtime(proc_handle_t* handle)
|
||||
{
|
||||
uintptr_t address = 0;
|
||||
|
||||
#ifdef MS_WINDOWS
|
||||
// On Windows, search for 'python' in executable or DLL
|
||||
address = search_windows_map_for_section(handle, "PyRuntime", L"python");
|
||||
if (address == 0) {
|
||||
// Error out: 'python' substring covers both executable and DLL
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
|
||||
}
|
||||
#elif defined(__linux__)
|
||||
// On Linux, search for 'python' in executable or DLL
|
||||
address = search_linux_map_for_section(handle, "PyRuntime", "python");
|
||||
if (address == 0) {
|
||||
// Error out: 'python' substring covers both executable and DLL
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to find the PyRuntime section in the process.");
|
||||
}
|
||||
#else
|
||||
// On macOS, try libpython first, then fall back to python
|
||||
address = search_map_for_section(handle, "PyRuntime", "libpython");
|
||||
if (address == 0) {
|
||||
// TODO: Differentiate between not found and error
|
||||
PyErr_Clear();
|
||||
address = search_map_for_section(handle, "PyRuntime", "python");
|
||||
}
|
||||
#endif
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
// Platform-independent memory read function
|
||||
static int
|
||||
read_memory(proc_handle_t *handle, uint64_t remote_address, size_t len, void* dst)
|
||||
{
|
||||
#ifdef MS_WINDOWS
|
||||
SIZE_T read_bytes = 0;
|
||||
SIZE_T result = 0;
|
||||
do {
|
||||
if (!ReadProcessMemory(handle->hProcess, (LPCVOID)(remote_address + result), (char*)dst + result, len - result, &read_bytes)) {
|
||||
PyErr_SetFromWindowsErr(0);
|
||||
return -1;
|
||||
}
|
||||
result += read_bytes;
|
||||
} while (result < len);
|
||||
return 0;
|
||||
#elif defined(__linux__) && HAVE_PROCESS_VM_READV
|
||||
struct iovec local[1];
|
||||
struct iovec remote[1];
|
||||
Py_ssize_t result = 0;
|
||||
Py_ssize_t read_bytes = 0;
|
||||
|
||||
do {
|
||||
local[0].iov_base = (char*)dst + result;
|
||||
local[0].iov_len = len - result;
|
||||
remote[0].iov_base = (void*)(remote_address + result);
|
||||
remote[0].iov_len = len - result;
|
||||
|
||||
read_bytes = process_vm_readv(handle->pid, local, 1, remote, 1, 0);
|
||||
if (read_bytes < 0) {
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
return -1;
|
||||
}
|
||||
|
||||
result += read_bytes;
|
||||
} while ((size_t)read_bytes != local[0].iov_len);
|
||||
return 0;
|
||||
#elif defined(__APPLE__) && TARGET_OS_OSX
|
||||
Py_ssize_t result = -1;
|
||||
kern_return_t kr = mach_vm_read_overwrite(
|
||||
pid_to_task(handle->pid),
|
||||
(mach_vm_address_t)remote_address,
|
||||
len,
|
||||
(mach_vm_address_t)dst,
|
||||
(mach_vm_size_t*)&result);
|
||||
|
||||
if (kr != KERN_SUCCESS) {
|
||||
switch (kr) {
|
||||
case KERN_PROTECTION_FAILURE:
|
||||
PyErr_SetString(PyExc_PermissionError, "Not enough permissions to read memory");
|
||||
break;
|
||||
case KERN_INVALID_ARGUMENT:
|
||||
PyErr_SetString(PyExc_PermissionError, "Invalid argument to mach_vm_read_overwrite");
|
||||
break;
|
||||
default:
|
||||
PyErr_SetString(PyExc_RuntimeError, "Unknown error reading memory");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
Py_UNREACHABLE();
|
||||
#endif
|
||||
return _Py_RemoteDebug_ReadRemoteMemory(handle, remote_address, len, dst);
|
||||
}
|
||||
|
||||
// Platform-independent memory write function
|
||||
static int
|
||||
write_memory(proc_handle_t *handle, uintptr_t remote_address, size_t len, const void* src)
|
||||
{
|
||||
|
|
@ -886,16 +157,7 @@ read_offsets(
|
|||
uintptr_t *runtime_start_address,
|
||||
_Py_DebugOffsets* debug_offsets
|
||||
) {
|
||||
*runtime_start_address = get_py_runtime(handle);
|
||||
if (!*runtime_start_address) {
|
||||
if (!PyErr_Occurred()) {
|
||||
PyErr_SetString(
|
||||
PyExc_RuntimeError, "Failed to get PyRuntime address");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
size_t size = sizeof(struct _Py_DebugOffsets);
|
||||
if (0 != read_memory(handle, *runtime_start_address, size, debug_offsets)) {
|
||||
if (_Py_RemoteDebug_ReadDebugOffsets(handle, runtime_start_address, debug_offsets)) {
|
||||
return -1;
|
||||
}
|
||||
if (ensure_debug_offset_compatibility(debug_offsets)) {
|
||||
|
|
@ -1097,3 +359,4 @@ _PySysRemoteDebug_SendExec(int pid, int tid, const char *debugger_script_path)
|
|||
return rc;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue