Skip to content

Commit

Permalink
Test Windows Wheels and Fix symbol address resolution on Windows (#543)
Browse files Browse the repository at this point in the history
This adds CI for testing out windows wheels against various versions of python.

This also applies a fix to use RVA for symbol address resolution on windows (as identified by @akhramov in #672)
  • Loading branch information
benfred authored Nov 1, 2024
1 parent 969c4df commit ad69f92
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 14 deletions.
30 changes: 29 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,27 +215,55 @@ jobs:
3.13.0,
]
# TODO: also test windows
os: [ubuntu-20.04, macos-13]
os: [ubuntu-20.04, macos-13, windows-latest]
# some versions of python can't be tested on GHA with osx because of SIP:
exclude:
- os: windows-latest
python-version: 3.6.15
- os: windows-latest
python-version: 3.7.17
- os: windows-latest
python-version: 3.8.18
- os: windows-latest
python-version: 3.9.20
- os: windows-latest
python-version: 3.10.15
- os: macos-13
python-version: 3.11.10
- os: windows-latest
python-version: 3.11.10
- os: macos-13
python-version: 3.12.0
- os: windows-latest
python-version: 3.12.0
- os: macos-13
python-version: 3.12.1
- os: windows-latest
python-version: 3.12.1
- os: macos-13
python-version: 3.12.2
- os: windows-latest
python-version: 3.12.2
- os: macos-13
python-version: 3.12.3
- os: windows-latest
python-version: 3.12.3
- os: macos-13
python-version: 3.12.4
- os: windows-latest
python-version: 3.12.4
- os: macos-13
python-version: 3.12.5
- os: windows-latest
python-version: 3.12.5
- os: macos-13
python-version: 3.12.6
- os: windows-latest
python-version: 3.12.6
- os: macos-13
python-version: 3.12.7
- os: windows-latest
python-version: 3.12.7

steps:
- uses: actions/checkout@v2
Expand Down
22 changes: 18 additions & 4 deletions ci/update_python_test_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ def parse_version(v):

def get_github_python_versions():
versions_json = requests.get(_VERSIONS_URL).json()
raw_versions = [v["version"] for v in versions_json]

# windows platform support isn't great for older versions of python
# get a map of version: platform/arch so we can exclude here
platforms = {}
for v in versions_json:
platforms[v["version"]] = set((f["platform"], f["arch"]) for f in v["files"])

raw_versions = [v["version"] for v in versions_json]
minor_versions = defaultdict(list)

for version_str in raw_versions:
Expand Down Expand Up @@ -46,11 +52,13 @@ def get_github_python_versions():

versions.extend(f"{major}.{minor}.{patch}" for patch in patches)

return versions
return versions, platforms


def update_python_test_versions():
versions = sorted(get_github_python_versions(), key=parse_version)
versions, platforms = get_github_python_versions()
versions = sorted(versions, key=parse_version)

build_yml_path = (
pathlib.Path(__file__).parent.parent / ".github" / "workflows" / "build.yml"
)
Expand Down Expand Up @@ -82,9 +90,15 @@ def update_python_test_versions():
# since it currently fails in GHA on SIP errors
exclusions = []
for v in versions:
if v.startswith("3.11.10") or v.startswith("3.12"):
# if we don't have a python version for osx/windows skip
if ("darwin", "x64") not in platforms[v] or v.startswith("3.12"):
exclusions.append(" - os: macos-13\n")
exclusions.append(f" python-version: {v}\n")

if ("win32", "x64") not in platforms[v] or v.startswith("3.12"):
exclusions.append(" - os: windows-latest\n")
exclusions.append(f" python-version: {v}\n")

first_exclude_line = lines.index(" exclude:\n", first_line)
last_exclude_line = lines.index("\n", first_exclude_line)
lines = lines[: first_exclude_line + 1] + exclusions + lines[last_exclude_line:]
Expand Down
6 changes: 2 additions & 4 deletions src/binary_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,8 @@ pub fn parse_binary(filename: &Path, addr: u64, size: u64) -> Result<BinaryInfo,
Object::PE(pe) => {
for export in pe.exports {
if let Some(name) = export.name {
if let Some(export_offset) = export.offset {
if let Some(addr) = offset.checked_add(export_offset as u64) {
symbols.insert(name.to_string(), addr);
}
if let Some(addr) = offset.checked_add(export.rva as u64) {
symbols.insert(name.to_string(), addr);
}
}
}
Expand Down
18 changes: 16 additions & 2 deletions src/python_process_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,14 @@ impl PythonProcessInfo {
let map = maps.iter().find(|m| {
if let Some(pathname) = m.filename() {
if let Some(pathname) = pathname.to_str() {
return is_python_bin(pathname) && m.is_exec();
#[cfg(not(windows))]
{
return is_python_bin(pathname) && m.is_exec();
}
#[cfg(windows)]
{
return is_python_bin(pathname);
}
}
}
false
Expand Down Expand Up @@ -139,7 +146,14 @@ impl PythonProcessInfo {
let libmap = maps.iter().find(|m| {
if let Some(pathname) = m.filename() {
if let Some(pathname) = pathname.to_str() {
return is_python_lib(pathname) && m.is_exec();
#[cfg(not(windows))]
{
return is_python_lib(pathname) && m.is_exec();
}
#[cfg(windows)]
{
return is_python_lib(pathname);
}
}
}
false
Expand Down
10 changes: 7 additions & 3 deletions tests/integration_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,25 @@ def _sample_process(self, script_name, options=None, include_profile_name=False)
# record option, and setting different flags. To get the profile output
# we're using the speedscope format (since we can read that in as json)
with tempfile.NamedTemporaryFile() as profile_file:
filename = profile_file.name
if sys.platform.startswith("win"):
filename = "profile.json"

cmdline = [
PYSPY,
"record",
"-o",
profile_file.name,
filename,
"--format",
"speedscope",
"-d",
"2",
]
cmdline.extend(options or [])
cmdline.extend(["--", sys.executable, script_name])
env = dict(os.environ, RUST_LOG="debug")
env = dict(os.environ, RUST_LOG="info")
subprocess.check_output(cmdline, env=env)
with open(profile_file.name) as f:
with open(filename) as f:
profiles = json.load(f)

frames = profiles["shared"]["frames"]
Expand Down

0 comments on commit ad69f92

Please sign in to comment.