Skip to content

Commit

Permalink
Clear the /DEPENDENTLOADFLAG value
Browse files Browse the repository at this point in the history
If a vendored DLL was built with a non-0 value for `/DEPENDENTLOADFLAG`,
then it can fail to load because it changes the DLL search path for its
dependencies. Modify any vendored DLLs (other than those specified with
`--add-dll`) with non-0 `/DEPENDENTLOADFLAG` to clear the value to 0.
This ensures that the default search path for Python extension modules
is used.
  • Loading branch information
adang1345 committed Aug 6, 2024
1 parent 2c49a5c commit 5182fe4
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 55 deletions.
51 changes: 49 additions & 2 deletions delvewheel/_dll_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,40 @@ def get_all_needed(lib_path: str,
return discovered, associated, ignored, not_found


def clear_dependent_load_flag(lib_path: str, verbose: int):
"""If the DLL given by lib_path was built with a non-0 value for
/DEPENDENTLOADFLAG, then modify the DLL as if /DEPENDENTLOADFLAG was 0, fix
the PE header checksum, and clear any signatures.
lib_path: path to the DLL
verbose: verbosity level, 0 to 2"""
with PEContext(lib_path, None, False, verbose) as pe:
pe.parse_data_directories([pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']])
if not hasattr(pe, 'DIRECTORY_ENTRY_LOAD_CONFIG') or not pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.Reserved1:
return
if verbose >= 1:
print(f'clearing /DEPENDENTLOADFLAG={pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.Reserved1:#x} for {os.path.basename(lib_path)}')
pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.Reserved1 = 0

# determine whether to remove signatures from overlay
pe_size = max(section.PointerToRawData + section.SizeOfRawData for section in pe.sections)
cert_table = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']]
truncate = cert_table.VirtualAddress == _round_to_next(pe_size, _ATTRIBUTE_CERTIFICATE_TABLE_ALIGNMENT) and cert_table.VirtualAddress + cert_table.Size == os.path.getsize(lib_path)

# clear reference to attribute certificate table if it exists
cert_table.VirtualAddress = 0
cert_table.Size = 0

lib_data = pe.write()
if truncate:
lib_data = lib_data[:pe_size]

# fix the checksum
with PEContext(None, lib_data, False, verbose) as pe:
pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum()
pe.write(lib_path)


def _round_to_next(size: int, alignment: int) -> int:
"""Return smallest n such that n % alignment == 0 and n >= size."""
if size % alignment == 0:
Expand Down Expand Up @@ -479,8 +513,13 @@ def _get_pe_size_and_enough_padding(pe: pefile.PE, new_dlls: typing.Iterable[byt

def replace_needed(lib_path: str, old_deps: typing.List[str], name_map: typing.Dict[str, str], strip: bool, verbose: int, test: typing.List[str]) -> None:
"""For the DLL at lib_path, replace its declared dependencies on old_deps
with those in name_map.
old_deps: a subset of the dependencies that lib_path has, in list form
with those in name_map. Also, if the DLL was built with a value other than
0 for /DEPENDENTLOADFLAG, then modify the DLL as if /DEPENDENTLOADFLAG was
0.
old_deps: a subset of the dependencies that lib_path has, in list form. Can
be empty, in which case the only thing we do is clear the
/DEPENDENTLOADFLAG value if it's non-0.
name_map: a dict that maps an old dependency name to a new name, must
contain at least all the keys in old_deps
strip: whether to try to strip DLLs that contain overlays if not enough
Expand All @@ -489,6 +528,7 @@ def replace_needed(lib_path: str, old_deps: typing.List[str], name_map: typing.D
test: testing options for internal use"""
if not old_deps:
# no dependency names to change
clear_dependent_load_flag(lib_path, verbose)
return
name_map = {dep.lower().encode('utf-8'): name_map[dep].encode('utf-8') for dep in old_deps}
# keep only the DLLs that will be mangled
Expand Down Expand Up @@ -668,6 +708,13 @@ def replace_needed(lib_path: str, old_deps: typing.List[str], name_map: typing.D
cert_table.VirtualAddress = 0
cert_table.Size = 0

# clear /DEPENDENTLOADFLAG value
pe.parse_data_directories([pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']])
if hasattr(pe, 'DIRECTORY_ENTRY_LOAD_CONFIG') and pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.Reserved1:
if verbose >= 1:
print(f'clearing /DEPENDENTLOADFLAG={pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.Reserved1:#x} for {os.path.basename(lib_path)}')
pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.Reserved1 = 0

# all changes to headers are done; serialize the PE file
lib_data = pe.write()

Expand Down
20 changes: 13 additions & 7 deletions delvewheel/_wheel_repair.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,24 +847,30 @@ def repair(
with open(os.path.join(libs_dir, lib_name), 'rb') as lib_file:
root = f'{root}-{self._hashfile(lib_file)}'
name_mangler[lib_name.lower()] = root + ext
for extension_module_path in extension_module_paths:
for extension_module_path in extension_module_paths:
if no_mangle_all:
needed = []
else:
extension_module_name = os.path.basename(extension_module_path)
if self._verbose >= 1:
print(f'repairing {extension_module_name} -> {extension_module_name}')
needed = _dll_utils.get_direct_mangleable_needed(extension_module_path, self._no_dlls, no_mangles, self._verbose)
_dll_utils.replace_needed(extension_module_path, needed, name_mangler, strip, self._verbose, self._test)
for lib_name in dependency_names:
_dll_utils.replace_needed(extension_module_path, needed, name_mangler, strip, self._verbose, self._test)
for lib_name in dependency_names:
lib_path = os.path.join(libs_dir, lib_name)
if no_mangle_all:
needed = []
else:
# lib_name is NOT lowercased
if self._verbose >= 1:
if lib_name.lower() in name_mangler:
print(f'repairing {lib_name} -> {name_mangler[lib_name.lower()]}')
else:
print(f'repairing {lib_name} -> {lib_name}')
lib_path = os.path.join(libs_dir, lib_name)
needed = _dll_utils.get_direct_mangleable_needed(lib_path, self._no_dlls, no_mangles, self._verbose)
_dll_utils.replace_needed(lib_path, needed, name_mangler, strip, self._verbose, self._test)
if lib_name.lower() in name_mangler:
os.rename(lib_path, os.path.join(libs_dir, name_mangler[lib_name.lower()]))
_dll_utils.replace_needed(lib_path, needed, name_mangler, strip, self._verbose, self._test)
if lib_name.lower() in name_mangler:
os.rename(lib_path, os.path.join(libs_dir, name_mangler[lib_name.lower()]))

if self._min_supported_python is None or self._min_supported_python < (3, 10):
load_order_filename = f'.load-order-{self._distribution_name}-{self._version}'
Expand Down
17 changes: 17 additions & 0 deletions tests/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,23 @@ def test_source_date_epoch(self):
remove('wheelhouse3/simpleext-0.0.1-cp312-cp312-win_amd64.whl')
remove('wheelhouse4/simpleext-0.0.1-cp312-cp312-win_amd64.whl')

def test_debug_load_flag(self):
"""/DEPENDENTLOADFLAG:0x800 is cleared in vendored DLL when name-
mangling is disabled"""
check_call(['delvewheel', 'repair', '--add-path', 'simpleext/x64/DEBUGLOADFLAG', '--no-mangle-all', 'simpleext/simpleext-0.0.1-cp312-cp312-win_amd64.whl'])
self.assertTrue(import_simpleext_successful())

def test_debug_load_flag2(self):
"""/DEPENDENTLOADFLAG:0x800 is cleared in vendored DLL when name-
mangling is enabled"""
check_call(['delvewheel', 'repair', '--add-path', 'simpleext/x64/DEBUGLOADFLAG', 'simpleext/simpleext-0.0.1-cp312-cp312-win_amd64.whl'])
self.assertTrue(import_simpleext_successful())

def test_debug_load_flag3(self):
"""/DEPENDENTLOADFLAG:0x800 is cleared in .pyd file"""
check_call(['delvewheel', 'repair', '--add-path', 'simpleext/x64', 'simpleext/simpleext-0.0.1-0dlf-cp312-cp312-win_amd64.whl'])
self.assertTrue(import_simpleext_successful('0dlf'))


class NeededTestCase(unittest.TestCase):
"""Tests for delvewheel needed"""
Expand Down
2 changes: 2 additions & 0 deletions tests/simpleext/build/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

n = '' # change to build different module name
py_limited_api = False # set to True and add --py-limited-api=cp3__ to command line to build with Python limited API
debug_load_flag = '0' # change to set a different /DEPENDENTLOADFLAG linker option

if sys.maxsize > 2**32:
library_dirs = ['simpledll/x64/Release']
Expand Down Expand Up @@ -39,6 +40,7 @@
ext_modules=[Extension(
f'simpleext{n}', [f'simpleext.c'],
include_dirs=['simpledll'],
extra_link_args=[f'/DEPENDENTLOADFLAG:{debug_load_flag}'],
libraries=['simpledll'],
library_dirs=library_dirs,
define_macros=define_macros,
Expand Down
14 changes: 10 additions & 4 deletions tests/simpleext/build/simpledll/simpledll.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,26 @@ Global
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
ReleaseDLF|Any CPU = ReleaseDLF|Any CPU
ReleaseDLF|x64 = ReleaseDLF|x64
ReleaseDLF|x86 = ReleaseDLF|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|Any CPU.ActiveCfg = Debug|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|Any CPU.Build.0 = Debug|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|x64.ActiveCfg = Debug|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|x64.Build.0 = Debug|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|x86.ActiveCfg = Debug|Win32
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|x86.Build.0 = Debug|Win32
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Debug|x86.ActiveCfg = Debug|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|Any CPU.ActiveCfg = Release|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|Any CPU.Build.0 = Release|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|x64.ActiveCfg = Release|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|x64.Build.0 = Release|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|x86.ActiveCfg = Release|Win32
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|x86.Build.0 = Release|Win32
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.Release|x86.ActiveCfg = Release|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.ReleaseDLF|Any CPU.ActiveCfg = ReleaseDLF|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.ReleaseDLF|Any CPU.Build.0 = ReleaseDLF|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.ReleaseDLF|x64.ActiveCfg = ReleaseDLF|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.ReleaseDLF|x64.Build.0 = ReleaseDLF|x64
{FF6B2276-D9EC-444D-AF0B-A736F8CC1E88}.ReleaseDLF|x86.ActiveCfg = ReleaseDLF|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
57 changes: 15 additions & 42 deletions tests/simpleext/build/simpledll/simpledll.vcxproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
<ProjectConfiguration Include="ReleaseDLF|x64">
<Configuration>ReleaseDLF</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
Expand All @@ -26,26 +22,20 @@
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLF|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
Expand All @@ -57,24 +47,21 @@
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLF|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;SIMPLEDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>_DEBUG;SIMPLEDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
Expand All @@ -85,13 +72,13 @@
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;SIMPLEDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>NDEBUG;SIMPLEDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
Expand All @@ -104,22 +91,7 @@
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;SIMPLEDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseDLF|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
Expand All @@ -136,6 +108,7 @@
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalOptions>/DEPENDENTLOADFLAG:0x800 %(AdditionalOptions)</AdditionalOptions>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
Expand Down
Binary file not shown.
Binary file added tests/simpleext/x64/DEBUGLOADFLAG/simpledll.dll
Binary file not shown.

0 comments on commit 5182fe4

Please sign in to comment.