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

Invoking zig cc with wine errors #17535

Open
moosichu opened this issue Oct 15, 2023 · 5 comments
Open

Invoking zig cc with wine errors #17535

moosichu opened this issue Oct 15, 2023 · 5 comments
Labels
bug Observed behavior contradicts documented or intended behavior

Comments

@moosichu
Copy link
Contributor

Zig Version

0.12.0-dev.890+8c6b0271c

Steps to Reproduce and Observed Behavior

Given a c file (eg. aaa.c):

extern int agsg(int a) { return a; }
extern int ssss(int a) { return a; }
extern int aaaa(int a) { return a; }

running:
zig.exe cc aaa.c

but specifically through wine, results in the following error:

error: unable to find zig self exe path: Unexpected

Expected Behavior

For zig to execute normally through wine.

Version of wine tested is 8.17 on Fedora.

Main use case is that I'm keen to test windows features on zig but no longer have access to windows.

@moosichu moosichu added the bug Observed behavior contradicts documented or intended behavior label Oct 15, 2023
@moosichu
Copy link
Contributor Author

moosichu commented Oct 15, 2023

I'm going to investigate this... if this is hopefully just a matter of resolving this one issue hopefully this use case can work! (although I just realised it might be tricky to build zig for windows without easy access to windows myself... might need to tackle this one later if I do get access to it at some point...)

Just realised it might just be an issue with selfExePathAlloc... so will try to repro it in a standalone program!

@moosichu
Copy link
Contributor Author

So the bug seems to be in GetFinalPathNameByHandle when called by wToPrefixedFileW when running on wine.

There's specifically this section:

// TODO find out if a path can start with something besides `\Device\<volume name>`,
// and if we need to handle it differently
// (i.e. how to determine the start and end of the volume name in that case)
if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected;

and this assumption is violated as the path returned is of the form \??\Z:\absolute\linux\path\ for me at least!

@leroycep
Copy link
Contributor

leroycep commented Jan 12, 2025

Edit: Sorry, I was just wrong about this one. The problem here is that Length is the length in bytes, not in UTF-16LE codepoints.

I just reported what I think is the source of the problem upstream to wine. Here's a quick program that demonstrates the problem:

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

pub fn main() !void {
    const image_path_name = std.os.windows.peb().ProcessParameters.ImagePathName;
    std.log.info("self path: Length = {}, MaximumLength = {}", .{ image_path_name.Length, image_path_name.MaximumLength });

    const buffer = image_path_name.Buffer.?[0..image_path_name.Length];
    std.log.info("self path: Buffer = {}", .{std.unicode.fmtUtf16Le(buffer)});
    std.debug.dumpHex(std.mem.sliceAsBytes(buffer));
}
00c8:err:ntoskrnl:ZwLoadDriver failed to create driver L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\winebth": c0000135
003c:fixme:service:scmdatabase_autostart_services Auto-start service L"winebth" failed to start: 126
info: self path: Length = 162, MaximumLength = 164
info: self path: Buffer = Z:\home\geemili\code\zig\build\example-dump-stacktrace\print-self-exe-minimal.exe?"Z:\home\geemili\code\zig\build\example-dump-sta
cktrace\print-self-exe-minimal.e
00007fffff122c88  5A 00 3A 00 5C 00 68 00  6F 00 6D 00 65 00 5C 00  Z.:.\.h.o.m.e.\.
00007fffff122c98  67 00 65 00 65 00 6D 00  69 00 6C 00 69 00 5C 00  g.e.e.m.i.l.i.\.
00007fffff122ca8  63 00 6F 00 64 00 65 00  5C 00 7A 00 69 00 67 00  c.o.d.e.\.z.i.g.
00007fffff122cb8  5C 00 62 00 75 00 69 00  6C 00 64 00 5C 00 65 00  \.b.u.i.l.d.\.e.
00007fffff122cc8  78 00 61 00 6D 00 70 00  6C 00 65 00 2D 00 64 00  x.a.m.p.l.e.-.d.
00007fffff122cd8  75 00 6D 00 70 00 2D 00  73 00 74 00 61 00 63 00  u.m.p.-.s.t.a.c.
00007fffff122ce8  6B 00 74 00 72 00 61 00  63 00 65 00 5C 00 70 00  k.t.r.a.c.e.\.p.
00007fffff122cf8  72 00 69 00 6E 00 74 00  2D 00 73 00 65 00 6C 00  r.i.n.t.-.s.e.l.
00007fffff122d08  66 00 2D 00 65 00 78 00  65 00 2D 00 6D 00 69 00  f.-.e.x.e.-.m.i.
00007fffff122d18  6E 00 69 00 6D 00 61 00  6C 00 2E 00 65 00 78 00  n.i.m.a.l...e.x.
00007fffff122d28  65 00 00 00 22 00 5A 00  3A 00 5C 00 68 00 6F 00  e...".Z.:.\.h.o.
00007fffff122d38  6D 00 65 00 5C 00 67 00  65 00 65 00 6D 00 69 00  m.e.\.g.e.e.m.i.
00007fffff122d48  6C 00 69 00 5C 00 63 00  6F 00 64 00 65 00 5C 00  l.i.\.c.o.d.e.\.
00007fffff122d58  7A 00 69 00 67 00 5C 00  62 00 75 00 69 00 6C 00  z.i.g.\.b.u.i.l.
00007fffff122d68  64 00 5C 00 65 00 78 00  61 00 6D 00 70 00 6C 00  d.\.e.x.a.m.p.l.
00007fffff122d78  65 00 2D 00 64 00 75 00  6D 00 70 00 2D 00 73 00  e.-.d.u.m.p.-.s.
00007fffff122d88  74 00 61 00 63 00 6B 00  74 00 72 00 61 00 63 00  t.a.c.k.t.r.a.c.
00007fffff122d98  65 00 5C 00 70 00 72 00  69 00 6E 00 74 00 2D 00  e.\.p.r.i.n.t.-.
00007fffff122da8  73 00 65 00 6C 00 66 00  2D 00 65 00 78 00 65 00  s.e.l.f.-.e.x.e.
00007fffff122db8  2D 00 6D 00 69 00 6E 00  69 00 6D 00 61 00 6C 00  -.m.i.n.i.m.a.l.
00007fffff122c78  2E 00 65 00                                       ..e.

The executable path in ImagePathName is doubled up. However, the path is null terminated, which might be why no one's noticed this until now.

@leroycep
Copy link
Contributor

leroycep commented Jan 12, 2025

Follow up, my previous post was incorrect, I was simply treating ImagePathName.Length as the codepoint length instead of the byte length.

However, I did some further digging and was able to get backtraces working on wine, though I'm pretty sure the logic isn't correct.

diff --git a/lib/std/debug/SelfInfo.zig b/lib/std/debug/SelfInfo.zig
index 544cf0ac6f..7f07ea1e8b 100644
--- a/lib/std/debug/SelfInfo.zig
+++ b/lib/std/debug/SelfInfo.zig
@@ -1003,7 +1003,7 @@ fn readCoffDebugInfo(allocator: Allocator, coff_obj: *coff.Coff) !Module {
             di.dwarf = dwarf;
         }
 
-        const raw_path = try coff_obj.getPdbPath() orelse return di;
+        const raw_path = (coff_obj.getPdbPath() catch return error.InvalidDebugInfo) orelse return di;
         const path = blk: {
             if (fs.path.isAbsolute(raw_path)) {
                 break :blk raw_path;
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
index ceed0618d1..381e722cda 100644
--- a/lib/std/os/windows.zig
+++ b/lib/std/os/windows.zig
@@ -1292,6 +1292,17 @@ pub fn GetFinalPathNameByHandle(
             return final_path;
         },
         .Dos => {
+            // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-even/c1550f98-a1ce-426a-9991-7509e7c3787c
+            const nt_unc_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\UNC\\");
+            const nt_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\");
+            if (mem.startsWith(u16, final_path, nt_unc_prefix)) {
+                // replace the 'C' in "UNC" with a backslash to convert it to DOS UNC syntax `\\servername\``
+                final_path[nt_unc_prefix.len - 2] = '\\';
+                return final_path[nt_unc_prefix.len - 2 ..];
+            } else if (mem.startsWith(u16, final_path, nt_prefix)) {
+                return final_path[nt_prefix.len..];
+            }
+
             // parse the string to separate volume path from file path
             const expected_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\");

So the bug seems to be in GetFinalPathNameByHandle when called by wToPrefixedFileW when running on wine.

There's specifically this section:

// TODO find out if a path can start with something besides `\Device\<volume name>`,
// and if we need to handle it differently
// (i.e. how to determine the start and end of the volume name in that case)
if (!mem.eql(u16, expected_prefix, final_path[0..expected_prefix.len])) return error.Unexpected;

and this assumption is violated as the path returned is of the form \??\Z:\absolute\linux\path\ for me at least!

I found three articles related to this syntax; a stackoverflow post, a section in Microsoft's EventLog Remoting Protocol, and a blog post on converting win32 paths to NT paths referenced by said stackoverflow post.


Okay the more I read the less I understand. Why is openSelfExe normalizing the path at all? Why not simply pass the ImageProcessPath to openFileW?

diff --git a/lib/std/fs.zig b/lib/std/fs.zig
index 99936e9abd..de81741f15 100644
--- a/lib/std/fs.zig
+++ b/lib/std/fs.zig
@@ -475,8 +475,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
         // the file, we can let the openFileW call follow the symlink for us.
         const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName;
         const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
-        const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name);
-        return cwd().openFileW(prefixed_path_w.span(), flags);
+        return cwd().openFileW(image_path_name.span(), flags);
     }
     // Use of max_path_bytes here is valid as the resulting path is immediately
     // opened with no modification.

My only thought is that perhaps it was copying selfExeDirPath, which probably needs that to find lib/std.

@leroycep
Copy link
Contributor

Relevant pull request: #17541

Relevant wine issue: https://bugs.winehq.org/show_bug.cgi?id=39569

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Observed behavior contradicts documented or intended behavior
Projects
None yet
Development

No branches or pull requests

2 participants