fixPath
is a tool to modify the path to a certain DLLs (Dynamic Shared Objects) for Microsoft Windows
Executables by rewriting parts of the executable's PE
header, when the .fixPath
section is present and indicates support for such rewrite, but without having to realign
the PE headers. In concept, it is similar to patchelf but instead of
changing search paths, fixPath hard-codes each DLL to a particular filepath.
The 'fix' in fixPath as in:
fix - fasten (something) securely in a particular place or position.
List all imports:
.\fixPath.exe list-imports ..\..\tests\program_with_lib\build\test_mylib.exe
TARGET:
- ..\..\tests\program_with_lib\build\test_mylib.exe
- fixPath version: 2
- fix_path_size: 301
IMPORTS
- 1, mylib.dll @ 0x4274
- 2, KERNEL32.dll @ 0x43c1
- 3, VCRUNTIME140D.dll @ 0x450e
- 4, ucrtbased.dll @ 0x465b
DELAYED IMPORTS
- 1, delayedlib.dll @ 0x3894
Change the delayedlib.dll
import location to an absolute path:
.\fixPath.exe set-import ..\..\tests\program_with_lib\build\test_mylib.exe delayedlib.dll c:\delayedlib.dll
TARGET:
- ..\..\tests\program_with_lib\build\test_mylib.exe
CHANGE DELAYED IMPORTS
- delayedlib.dll @ 0x3894 -> c:\delayedlib.dll
DONE
Use fixPath
to modify the library search path:
- Microsoft's linker default search path(s)., i.e. c:\Windows\System32 and similar
- relative path to the executable: lib\delayedlib.dll
- absoluate path in filesystem: c:\lib\delayedlib.dll
Note: fixPath
preserves the original library name and appends this information to the .idata
section of the PE header.
fixPath
requires special support from the linker lld
- the LLVM linker. It can't be applied to already existing binaries.
The fixPath
tool relies on a custom PE extension section called .fixPath
(a section like .idata
or .didata
).
A patched version of the ldd linker or ld linker will create this section:
- when passing
/useFixPath
on the command line the.fixPath
section is created - when passing the
/fixPathSize:33
on the command line, thefixPathSize
can be set
The fixPath
tool will check if the .fixPath
field exists and if so one can use it to change the loader order from:
- The library filename, for instance
KERNEL32.dll
will be searched in the default locations, likeC:\Windows\System32
. - However, it can be a relative filepath like
..\foo.dll
or - It can be an absolute filepath like
c:\bar.dll
The motivation of the .fixPath
section is to have a place with the original dll names
in case the user needs to revert.
This .fixPath
section contains:
version field
[u32],fixPathSize
[u32]idata_name_table_size
[u32]didata_name_table_size
[u32]- array [
idataNameTable dllname
] [ascii] - array [
didataNameTable dllname
] [ascii], - bytes padding [u32]
- URL of fixPath https://github.com/nixcloud/fixPath
The motivation of the .fixPath
section is to have a place with the original dll names
in case the user needs to revert.
This .fixPath
section contains:
- a
version field
[u32],fixPathSize
[u32], empty spacer [u64] idata_name_table_size
[u32]- array [
idataNameTable dllname
] [ascii] - bytes padding [u32]
didata_name_table_size
[u32]- array [
didataNameTable dllname
] [ascii], - bytes padding [u32]
- URL of fixPath https://github.com/nixcloud/fixPath
- bytes padding [u32]
See https://github.com/qknight/llvm-project/tree/libnix_PE-fixPath
Features:
- Reserving 300 chars dllname space
- Adding
.fixPath
section with version and dllname size - Adding
.fixPath
section with dllnames for dll loading - Adding
.fixPath
section with dllnames for delayed dll loading - Using llm options to represent defaults
- Command line switches for cmake & similar
- Command line switches for clang-cl
- ImHex: add support
delayload parsing
- ImHex: add support
.fixPath parsing
- GNU LD (GNU compiler) support
- GNU gold (GNU compiler) support
- Visual Studio linker support
No prototypes yet.
The tests/
directory contains a set of executables which were used for verifying the fixPath execution.
It uses CMake
with these settings:
set (CMAKE_CPP_FLAGS "-Xlinker /useFixPath")
set (CMAKE_C_FLAGS "-Xlinker /useFixPath")
Optionally one can override the reserved fixPath size default of 300 chars to 333 chars:
set (CMAKE_CPP_FLAGS "-Xlinker /useFixPath -Xlinker /fixPathSize:333")
set (CMAKE_C_FLAGS "-Xlinker /useFixPath -Xlinker /fixPathSize:333")
A verbose ninja run:
$ cmake -G "Ninja" -D_CMAKE_TOOLCHAIN_PREFIX=llvm- .. && ninja --verbose
-- The C compiler identification is Clang 18.1.6 with GNU-like command-line
-- The CXX compiler identification is Clang 18.1.6 with GNU-like command-line
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: C:/Program Files/LLVM/bin/clang.exe - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/LLVM/bin/clang++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/joschie/Desktop/Projects/binutils-ld-experiments/build
[1/4] C:\PROGRA~1\LLVM\bin\clang.exe -IC:/Users/joschie/Desktop/Projects/binutils-ld-experiments/include -Xlinker /useFixPath -Xlinker /fixPathSize:333 -g -Xclang -gcodeview -O0 -D_DEBUG -D_DLL -D_MT -Xclang --dependent-lib=msvcrtd -MD -MT CMakeFiles/test_mylib.dir/src/main.c.obj -MF CMakeFiles\test_mylib.dir\src\main.c.obj.d -o CMakeFiles/test_mylib.dir/src/main.c.obj -c C:/Users/joschie/Desktop/Projects/binutils-ld-experiments/src/main.c
clang: warning: -Xlinker /useFixPath: 'linker' input unused [-Wunused-command-line-argument]
clang: warning: -Xlinker /fixPathSize:333: 'linker' input unused [-Wunused-command-line-argument]
[2/4] C:\PROGRA~1\LLVM\bin\clang.exe -DMYLIB_EXPORTS -Dmylib_EXPORTS -IC:/Users/joschie/Desktop/Projects/binutils-ld-experiments/include -Xlinker /useFixPath -Xlinker /fixPathSize:333 -g -Xclang -gcodeview -O0 -D_DEBUG -D_DLL -D_MT -Xclang --dependent-lib=msvcrtd -MD -MT CMakeFiles/mylib.dir/src/mylib.c.obj -MF CMakeFiles\mylib.dir\src\mylib.c.obj.d -o CMakeFiles/mylib.dir/src/mylib.c.obj -c C:/Users/joschie/Desktop/Projects/binutils-ld-experiments/src/mylib.c
clang: warning: -Xlinker /useFixPath: 'linker' input unused [-Wunused-command-line-argument]
clang: warning: -Xlinker /fixPathSize:333: 'linker' input unused [-Wunused-command-line-argument]
[3/4] cmd.exe /C "cd . && C:\PROGRA~1\LLVM\bin\clang.exe -fuse-ld=lld-link -nostartfiles -nostdlib -Xlinker /useFixPath -Xlinker /fixPathSize:333 -g -Xclang -gcodeview -O0 -D_DEBUG -D_DLL -D_MT -Xclang --dependent-lib=msvcrtd -shared -o mylib.dll -Xlinker /MANIFEST:EMBED -Xlinker /implib:mylib.lib -Xlinker /pdb:mylib.pdb -Xlinker /version:1.0 CMakeFiles/mylib.dir/src/mylib.c.obj -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 -loldnames && cd ."
[4/4] cmd.exe /C "cd . && C:\PROGRA~1\LLVM\bin\clang.exe -fuse-ld=lld-link -nostartfiles -nostdlib -Xlinker /useFixPath -Xlinker /fixPathSize:333 -g -Xclang -gcodeview -O0 -D_DEBUG -D_DLL -D_MT -Xclang --dependent-lib=msvcrtd -Xlinker /subsystem:console CMakeFiles/test_mylib.dir/src/main.c.obj -o test_mylib.exe -Xlinker /MANIFEST:EMBED -Xlinker /implib:test_mylib.lib -Xlinker /pdb:test_mylib.pdb -Xlinker /version:0.0 mylib.lib -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 -loldnames && cd ."
Running the test program:
./test_mylib.exe
Hello from my_function!
Hello from the delayed loaded library!
Looking at sections:
objdump.exe -h .\test_mylib.exe
.\test_mylib.exe: file format pei-x86-64
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00002366 0000000140001000 0000000140001000 00000400 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rdata 00001334 0000000140004000 0000000140004000 00002800 2**4
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .data 00000200 0000000140006000 0000000140006000 00003c00 2**4
CONTENTS, ALLOC, LOAD, DATA
3 .pdata 0000039c 0000000140007000 0000000140007000 00003e00 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .fixPath 0000007b 0000000140008000 0000000140008000 00004200 2**2
CONTENTS, ALLOC, LOAD, DATA
5 .rsrc 000001a8 0000000140009000 0000000140009000 00004400 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
6 .reloc 00000030 000000014000a000 000000014000a000 00004600 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
Modifying main.exe's library entry (without extending the .rdata)
- dll import supports absolute paths
- dll import supports relative paths like
lib\lib.dll
(also checkt that it will uselib\lib.dll
overlib.dll
in same dir) - works with long filename : "c:\t\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.dll"
- works with long directory: "c:\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\t.dll"
- works with "C:\t\nix\store\zxxialnsgv0ahms5d35sivqzxqg1kicf-libiec61883-1.2.0\lib\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.dll" 213 chars
- works with "C:\t\nix\store\zxxialnsgv0ahms5d35sivqzxqg1kicf-libiec61883-1.2.0\lib\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.dll" 269 chars
this needs checking still:
- works with relative path ../zxxialnsgv0ahms5d35sivqzxqg1kicf-libiec61883-1.2.0\lib\lib.dll to symlinked library pointing to C:\nix\store\zxxialnsgv0ahms5d35sivqzxqg1kicf-libiec61883-1.2.0\lib\lib.dll
- works with relative path to symlinked library, which points also to a relative path ... and so on
/nix/store
as path:
- one can use fixPath --set-import test.exe mylib.dll /nix/store/zxxialnsgv0ahms5d35sivqzxqg1kicf-libiec61883-1.2.0/lib/mylib.dll and executed in a shell from c: this will work (form d: it won't)
- A single leading slash means "whichever drive is the current one". There is no "drive root" like the Unix "/" on Windows. If C: is the active drive than "/" is C:, if D: is the active drive than "/" is D:\ which is why it's considered relative on Windows.
When compiling software using the nix package manager, it is required to build the software from a
different folder than the final location of the software. Using fixPath
, which is similar to the rpath feature
from Linux/Unix the developer can hard-code the paths where the libraries are loaded from.
It basically prevents DLL-Hell by making it possible to use absolute paths for certain libraries and not rely on the Microsoft's linker default search path(s).
Modifying dll paths of arbitrary executables proved way too complicated and often times resulted in corrupt programs, because the required relocations of sections and addresses inside the sections is undocumented and not wanted.
https://discourse.llvm.org/t/build-lld-on-windows-10-using-ninja-llvm-18-1-6-cmake/79318/5
- 'fixPath' will force the Windows loader to use a 'fixed' down version of the library in an absolute or relative path
- 'rpath' sets search directories hierarchies thus works on paths. On Windows there is no such concept yet
See:
- https://developercommunity.visualstudio.com/idea/566616/support-rpath-for-binaries-during-development.html
- https://stackoverflow.com/questions/107888/is-there-a-windows-msvc-equivalent-to-the-rpath-linker-flag
Instead of using only fixPath it might be interesting to modify lld
to write absolute library paths,
i.e. c:\nix\store...\lib.dll' into the binary by default instead of just 'lib.dll' and hope for
the best.
- Martin Storsjö - https://github.com/mstorsjo
- John Ericson - https://github.com/ericson2314
- bring lld upstream
- bring object changes upstream
- imhex
- donate
- write fixpath section support