Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build libsodium.dll with VC++ for win-arm64 #1431

Closed
wants to merge 5 commits into from
Closed

Conversation

nil4
Copy link
Contributor

@nil4 nil4 commented Dec 4, 2024

Update buildbase.bat for win-arm64:

  • update version check to use greater-than-or-equal, i.e. include VS 2019 and 2022 (or later versions) -- ref. https://ss64.com/nt/if.html section Test Numeric values
  • select the ARM64 environment (x86_arm64 is not valid)

Update dotnet-core.yml for win-arm64:

nil4 added 2 commits December 4, 2024 13:37
- update version check to use **greater-than-or-equal**, i.e. to include VS 2019 **and** 2022 (or later versions)
- select the `ARM64` environment (`x86_arm64` is not valid)
Build `win-arm64` native library with VC++ on Windows; may help with #1430
@nil4
Copy link
Contributor Author

nil4 commented Dec 4, 2024

@jedisct1 @mlugg I may need your help with the GitHub CI failure in https://github.com/jedisct1/libsodium/actions/runs/12160019675

mlugg/setup-zig@v1 is not allowed to be used in jedisct1/libsodium. Actions in this workflow must be: within a repository owned by jedisct1, created by GitHub, verified in the GitHub Marketplace, or matching the following: goto-bus-stop/setup-zig@*, hecrj/setup-rust-action@*, softprops/action-gh-release@*, ShiftLeftSecurity/scan-action@*.

Is there anything I can do in my fork to get CI back on its feet and able to run on this PR? Thank you in advance!

@jedisct1
Copy link
Owner

jedisct1 commented Dec 4, 2024

Could we fix what's wrong with the files created by Zig instead?

@nil4
Copy link
Contributor Author

nil4 commented Dec 4, 2024

I do not know what's wrong with them, and I'm afraid I don't know how to debug Zig or native code. Happy to take a deeper look if someone can advise what exactly I should look at; I had hoped that by providing repro steps we'd have a better chance of figuring out what's happening.

In any case, given that in the past several other people mentioned win-arm64 didn't work for them, and they also didn't work on my machines, but the DLL built with VC++ does work, wouldn't trying to switch to VC++ artifacts be a step in the right direction, at least?

@jedisct1
Copy link
Owner

jedisct1 commented Dec 4, 2024

I understand that this hack may fix your problem on a specific architecture, but fixing the root cause would be better and would address a much broader problem possibly affecting other applications. If there's a bug in Zig that builds libraries that .NET cannot parse, it's a good opportunity to understand why.

As I don't use Windows and don't know anything about .NET, testing the Visual Studio builds is very painful. Cross-compiling everything with the same compiler makes things a little bit easier.

@nil4
Copy link
Contributor Author

nil4 commented Dec 4, 2024

I understand your point of view, and I admire Zig as a language and a toolset that can cross-compile for many platforms. For instance, we built libsodium for browser-wasm successfully on non-Windows platforms. It's unfortunate that win-arm64 doesn't seem to work yet.

Here is a little bit of information that I could gather from dumpbin comparing the libsodium.dll inside the NuGet package (built with Zig) and the one built locally with VC++

Comparing dumpbin /dependents i.e. which other DLLs does libsodium import:

Dependents: NuGet package (Zig)
> dumpbin /dependents C:\Users\nil4\.nuget\packages\libsodium\1.0.20\runtimes\win-arm64\native\libsodium.dll
Microsoft (R) COFF/PE Dumper Version 14.42.34435.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Users\nil4\.nuget\packages\libsodium\1.0.20\runtimes\win-arm64\native\libsodium.dll

File Type: DLL

  Image has the following dependencies:

    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-private-l1-1-0.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-time-l1-1-0.dll
    ADVAPI32.dll
    KERNEL32.dll
    api-ms-win-crt-stdio-l1-1-0.dll
    api-ms-win-crt-environment-l1-1-0.dll

  Summary

        1000 .buildid
        2000 .data
        1000 .debug_line
        1000 .pdata
       15000 .rdata
        1000 .reloc
       49000 .text
        1000 .tls
Dependents: locally-built (VC++)
> dumpbin /dependents C:\dev\libsodium_localbuild\bin\ARM64\Release\v143\dynamic\libsodium.dll
Microsoft (R) COFF/PE Dumper Version 14.42.34435.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\dev\libsodium_localbuild\bin\ARM64\Release\v143\dynamic\libsodium.dll

File Type: DLL

  Image has the following dependencies:

    ADVAPI32.dll
    KERNEL32.dll
    VCRUNTIME140.dll
    api-ms-win-crt-runtime-l1-1-0.dll
    api-ms-win-crt-string-l1-1-0.dll
    api-ms-win-crt-heap-l1-1-0.dll
    api-ms-win-crt-time-l1-1-0.dll

  Summary

        1000 .data
        1000 .pdata
       12000 .rdata
        1000 .reloc
        1000 .rsrc
       23000 .text

A couple of differences that could (perhaps?) be relevant:

  • the VC++ build links to VCRUNTIME140.dll -- this is usually how Windows apps look like.
  • the Zig-compiled DLL doesn't do that, instead it imports api-ms-win-crt-private-l1-1-0.dll -- the private seems a bit off.

One step further, the list of symbols imported from each dependent DLLs, from dumpbin /imports:

Imports: NuGet package (Zig)
> dumpbin /imports C:\Users\ni4\.nuget\packages\libsodium\1.0.20\runtimes\win-arm64\native\libsodium.dll
Microsoft (R) COFF/PE Dumper Version 14.42.34435.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\Users\ni4\.nuget\packages\libsodium\1.0.20\runtimes\win-arm64\native\libsodium.dll

File Type: DLL

  Section contains the following imports:

    api-ms-win-crt-heap-l1-1-0.dll
              1005C698 Import Address Table
              1005C4B0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 _set_new_mode
                           0 calloc
                           0 free
                           0 malloc

    api-ms-win-crt-private-l1-1-0.dll
              1005C6C0 Import Address Table
              1005C4D8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 memchr
                           0 strchr
                           0 strrchr

    api-ms-win-crt-runtime-l1-1-0.dll
              1005C6E0 Import Address Table
              1005C4F8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 __p___argc
                           0 __p___argv
                           0 __p___wargv
                           0 _configure_narrow_argv
                           0 _configure_wide_argv
                           0 _crt_at_quick_exit
                           0 _crt_atexit
                           0 _errno
                           0 _execute_onexit_table
                           0 _exit
                           0 _initialize_narrow_environment
                           0 _initialize_onexit_table
                           0 _initialize_wide_environment
                           0 _initterm
                           0 _register_onexit_function
                           0 abort
                           0 raise

    api-ms-win-crt-string-l1-1-0.dll
              1005C770 Import Address Table
              1005C588 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 strlen
                           0 strncmp

    api-ms-win-crt-time-l1-1-0.dll
              1005C788 Import Address Table
              1005C5A0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 __daylight
                           0 __timezone
                           0 __tzname
                           0 _ftime64
                           0 _tzset

    ADVAPI32.dll
              1005C7B8 Import Address Table
              1005C5D0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 SystemFunction036

    KERNEL32.dll
              1005C7C8 Import Address Table
              1005C5E0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 DeleteCriticalSection
                           0 EnterCriticalSection
                           0 GetLastError
                           0 GetSystemInfo
                           0 InitializeCriticalSection
                           0 LeaveCriticalSection
                           0 Sleep
                           0 TlsGetValue
                           0 VirtualAlloc
                           0 VirtualFree
                           0 VirtualLock
                           0 VirtualProtect
                           0 VirtualQuery
                           0 VirtualUnlock

    api-ms-win-crt-stdio-l1-1-0.dll
              1005C840 Import Address Table
              1005C658 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 __acrt_iob_func
                           0 __stdio_common_vfprintf
                           0 __stdio_common_vfwprintf
                           0 fwrite

    api-ms-win-crt-environment-l1-1-0.dll
              1005C868 Import Address Table
              1005C680 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                           0 __p__environ
                           0 __p__wenviron

  Summary

        1000 .buildid
        2000 .data
        1000 .debug_line
        1000 .pdata
       15000 .rdata
        1000 .reloc
       49000 .text
        1000 .tls
Imports: locally-built (VC++)
> dumpbin /imports C:\dev\libsodium_localbuild\bin\ARM64\Release\v143\dynamic\libsodium.dll
Microsoft (R) COFF/PE Dumper Version 14.42.34435.0
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file C:\dev\libsodium_localbuild\bin\ARM64\Release\v143\dynamic\libsodium.dll

File Type: DLL

  Section contains the following imports:

    ADVAPI32.dll
             180024000 Import Address Table
             1800357F0 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                         31A SystemFunction036

    KERNEL32.dll
             180024010 Import Address Table
             180035800 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                         38F InitializeCriticalSection
                         151 EnterCriticalSection
                         3EA LeaveCriticalSection
                         5BE Sleep
                         30E GetSystemInfo
                         60A VirtualAlloc
                         610 VirtualProtect
                         60D VirtualFree
                         60F VirtualLock
                         614 VirtualUnlock
                         47B QueryPerformanceCounter
                         23C GetCurrentProcessId
                         240 GetCurrentThreadId
                         314 GetSystemTimeAsFileTime
                         13C DisableThreadLibraryCalls
                         394 InitializeSListHead

    VCRUNTIME140.dll
             180024098 Import Address Table
             180035888 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          3B memcpy
                          3D memset
                          3C memmove
                          3F strchr
                          40 strrchr
                           8 __C_specific_handler
                          24 __std_type_info_destroy_list

    api-ms-win-crt-runtime-l1-1-0.dll
             1800240F8 Import Address Table
             1800358E8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          21 _errno
                          54 abort
                          36 _initterm
                          37 _initterm_e
                          3F _seh_filter_dll
                          18 _configure_narrow_argv
                          33 _initialize_narrow_environment
                          34 _initialize_onexit_table
                          22 _execute_onexit_table
                          16 _cexit

    api-ms-win-crt-string-l1-1-0.dll
             180024150 Import Address Table
             180035940 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          8E strncmp

    api-ms-win-crt-heap-l1-1-0.dll
             1800240D8 Import Address Table
             1800358C8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          19 malloc
                          18 free
                          17 calloc

    api-ms-win-crt-time-l1-1-0.dll
             180024160 Import Address Table
             180035950 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                          14 _ftime64

  Summary

        1000 .data
        1000 .pdata
       12000 .rdata
        1000 .reloc
        1000 .rsrc
       23000 .text

This shows that the Zig-built DLL imports a few functions from api-ms-win-crt-private-l1-1-0.dll:

  • memchr
  • strchr
  • strrchr

While the VC++-built DLL imports two of them (strchr and strrchr) from VCRUNTIME140.dll.

I do not know if this can be related to the fact that the DLL doesn't load as expected. Perhaps there is a C runtime API, specific to ARM64, that VCRUNTIME abstracts away from apps?

I read that Zig tries to minimize platform dependencies as much as possible, and on Windows strives to only use kernel-exposed APIs, and perhaps the DLL load failure might be related?

@nil4
Copy link
Contributor Author

nil4 commented Dec 4, 2024

The MS docs at https://learn.microsoft.com/en-us/cpp/windows/universal-crt-deployment?view=msvc-170#local-deployment mention this:

The DLLs for local deployment are included as part of the Windows SDK, in the Windows Kits\10\Redist\ucrt\DLLs subdirectory, by computer architecture. The DLLs required include ucrtbase.dll and a set of APISet forwarder DLLs named api-ms-win-*.dll. The set of DLLs required on each operating system varies.

Comparing the ARM64 folder there vs. x64/x86 shows that the api-ms-win*.dll files are only present in the latter, but not the former -- only ARM64\ucrtbase.dll is there.

That is, api-ms-win-crt-private-l1-1-0.dll isn't present in the Windows SDK ucrt redist folder for ARM64:

> cd "C:\Program Files (x86)\Windows Kits\10\Redist\10.0.26100.0\ucrt\DLLs"

> dir ARM64\api-ms-win-crt-private-l1-1-0.dll
File Not Found

> dir x64\api-ms-win-crt-private-l1-1-0.dll
2024-09-05  14:56            75,328 api-ms-win-crt-private-l1-1-0.dll
               1 File(s)         75,328 bytes

> dir x86\api-ms-win-crt-private-l1-1-0.dll
2024-09-05  14:56            67,624 api-ms-win-crt-private-l1-1-0.dll
               1 File(s)         67,624 bytes

I hope this helps, and am very much open to suggestion to anything else I could look at.

@nil4
Copy link
Contributor Author

nil4 commented Dec 4, 2024

@jedisct1 I tried removing .NET from the equation, and instead wrote a simple C program to load the arm64 libsodium.dll. I built and ran the following with VC++:

#include <stdio.h>
#include <windows.h>

int main()
{
    HMODULE module = LoadLibraryW(L"C:\\Users\\nil4\\.nuget\\packages\\libsodium\\1.0.20\\runtimes\\win-arm64\\native\\libsodium.dll");
    if (module == NULL) {
        printf("LoadLibrary failed: %08x\n", GetLastError());
        return 1;
    }

    typedef int(__cdecl *sodium_init_p)(void);

    sodium_init_p sodium_init = (sodium_init_p)GetProcAddress(module, "sodium_init");
    if (sodium_init == NULL) {
        printf("GetProcAddress: %08x\n", GetLastError());
        return 1;
    }

    int result = sodium_init();
    printf("sodium_init() returned %d", result);

    FreeLibrary(module);
    return 0;
}

This failed as well, showing a Windows error message box:

image

@nil4
Copy link
Contributor Author

nil4 commented Dec 10, 2024

I also tried loading libsodium.dll from a Zig program, but that did not work either. To reproduce:

> zig env
{
 [...]
 "version": "0.13.0", 
 "target": "aarch64-windows.win10_fe...win10_fe-gnu",
}

> mkdir reprozig && cd reprozig

> zig init
> zig fetch --save=zigwin32 https://github.com/marlersoft/zigwin32/archive/main.tar.gz

Replace the entire build.zig file contents with:

const builtin = @import("builtin");
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{ .default_target = .{ .os_tag = .windows, .cpu_arch = .aarch64 } });
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "repro-zig",
        .root_source_file = b.path("src/repro.zig"),
        .target = target,
        .optimize = optimize,
        .link_libc = false,
    });

    const zigwin32 = b.dependency("zigwin32", .{});
    exe.root_module.addImport("zigwin32", zigwin32.module("zigwin32"));

    b.installArtifact(exe);
}

Delete src/*.zig files, replacing them with this repro.zig:

const std = @import("std");
const win32 = struct {
    usingnamespace @import("zigwin32").zig;
    usingnamespace @import("zigwin32").foundation;
    usingnamespace @import("zigwin32").system.library_loader;
};
const L = win32.L;

pub fn main() void {
    const libsodium = L("C:\\Users\\nil4\\.nuget\\packages\\libsodium\\1.0.20\\runtimes\\win-arm64\\native\\libsodium.dll");

    const module: win32.HINSTANCE = win32.LoadLibraryW(libsodium) orelse {
        std.debug.panic("LoadLibrary failed: {}", .{win32.GetLastError()});
    };

    const ptr = win32.GetProcAddress(module, "sodium_init") orelse {
        std.debug.panic("GetProcAddress failed: {}", .{win32.GetLastError()});
    };

    const sodium_init_p = *const fn () callconv(.C) c_int;
    const sodium_init: sodium_init_p = @ptrCast(ptr);

    const result = sodium_init();
    std.debug.print("sodium_init() returned {}", .{result});

    _ = win32.FreeLibrary(module);
}

Build and run the program to confirm the issue:

> zig build && zig-out\bin\repro-zig.exe

thread 13708 panic: LoadLibrary failed: win32.foundation.WIN32_ERROR.ERROR_BAD_EXE_FORMAT
C:\dev\reprozig\src\repro.zig:13:24: 0x7ff78fa610b3 in main (repro-zig.exe.obj)
        std.debug.panic("LoadLibrary failed: {}", .{win32.GetLastError()});
                       ^
C:\dev\zig\lib\std\start.zig:363:53: 0x7ff78fa6100f in WinStartup (repro-zig.exe.obj)
    std.os.windows.ntdll.RtlExitUserProcess(callMain());
                                                    ^
???:?:?: 0x7ff88195873f in ??? (KERNEL32.DLL)
???:?:?: 0x7ff884cc0083 in ??? (ntdll.dll)

It looks like neither .NET, C or Zig are able to load the current win-arm64 DLL.

@jedisct1 if there is anything else I can do to help inform a decision about this PR, please let me know -- I would appreciate feedback.

@jedisct1
Copy link
Owner

I spent a couple hours fighting with UTM to run Windows.

And... the libsodium.dll file loads fine with the following code (intentionally using the Win32 APIs directly, even though Zig has std.DynLib to handle this)

const std = @import("std");
const windows = std.os.windows;

const W = std.unicode.utf8ToUtf16LeStringLiteral;

pub fn main() !void {
    const lib = try windows.LoadLibraryW(W("libsodium.dll"));
    const addr = windows.kernel32.GetProcAddress(lib, "sodium_version_string").?;
    const sodium_version_string: *fn () [*:0]u8 = @ptrCast(@alignCast(addr));
    std.debug.print("libsodium version: {s}\n", .{sodium_version_string()});
}
PS Z:\> .\dll-load
libsodium version: 1.0.20

So I'm really confused.

@jedisct1
Copy link
Owner

Ahah!

The DLL loads fine if I build it locally, but it fails with the libsodium.dll file built on CI for the NuGet package.

@nil4
Copy link
Contributor Author

nil4 commented Dec 10, 2024

Alright, was about to reply that now I'm stumped and starting to question my reality! Glad to hear that the difference is in fact there! 😌

I really appreciate you going the extra mile to set up UTM and ARM64 Windows -- good that it wasn't in vain after all! If there's anything I can do to help untangle more mysteries, I'm happy to chime in if I can.

@nil4
Copy link
Contributor Author

nil4 commented Dec 10, 2024

Could it be that the build host being ARM or non-ARM makes a difference to Zig? (Sounds like both of our local build hosts are ARM64 vs. GitHub CI's x64)

Or maybe the Zig version used in CI has an issue when cross-compiling to another Windows architecture? In any case, something's off with the CI binary -- good that we have it confirmed now!

@jedisct1
Copy link
Owner

Updating Zig in the CI to Zig 0.14 fixes the issue!

jedisct1 added a commit that referenced this pull request Dec 10, 2024
@jedisct1 jedisct1 closed this in 70eaf4d Dec 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants