xamarin / xamarin-macios

.NET for iOS, Mac Catalyst, macOS, and tvOS provide open-source bindings of the Apple SDKs for use with .NET managed languages such as C#
Other
2.44k stars 507 forks source link

Using PCSC nuget makes linking fail with clang error 1 #15118

Open chosl opened 2 years ago

chosl commented 2 years ago

When trying to use PCSC 6.0.0 nuget ( https://www.nuget.org/packages/PCSC/ https://github.com/danm-de/pcsc-sharp) in a MAUI MacCatalyst project build will fail with clang error 1. With Maui preview 14 it was possible to successfully build (with same project and same PCSC nuget version)

Steps to Reproduce

  1. Create new maui project (dotnet new maui -n MauiPCSC)
  2. Add reference to PCSC 6.0.0 nuget
  3. Build against MacCatalyst target (dotnet build -f net6.0-maccatalyst -v d /bl:msbuild.binlog)

Expected Behavior

Successful build

Actual Behavior

Build fails in the linking stage

Build failed:  error : clang++ exited with code 1
Tool xcrun execution finished (exit code = 1).
ld: building for Mac Catalyst, but linking in .tbd built for macOS, file '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/System/Library/Frameworks//PCSC.framework/PCSC.tbd' for architecture x86_64
         clang: error: linker command failed with exit code 1 (use -v to see invocation)

Environment

Version information Visual Studio Enterprise 2022 for Mac Preview Version 17.3 Preview (17.3 build 191) Installation UUID: f7a2a130-2329-430a-8fcc-3d90486174aa Runtime .NET 6.0.3 (64-bit) Architecture: Arm64 Roslyn (Language Service) 4.2.0-3.22181.8+a59a22c6f3c2cc1883e20cece412209f18f4a29f NuGet Version: 6.0.0.262 .NET SDK (Arm64) SDK: /usr/local/share/dotnet/sdk/6.0.300/Sdks SDK Version: 6.0.300 MSBuild SDKs: /usr/local/share/dotnet/sdk/6.0.300/Sdks .NET SDK (x64) SDK Version: 3.1.419 .NET Runtime (Arm64) Runtime: /usr/local/share/dotnet/dotnet Runtime Version: 6.0.5 .NET Runtime (x64) Runtime: /usr/local/share/dotnet/x64/dotnet Runtime Versions: 6.0.5 6.0.4 6.0.3 6.0.2 6.0.1 5.0.17 5.0.16 5.0.15 5.0.14 5.0.13 3.1.25 3.1.24 3.1.23 3.1.22 Xamarin.Profiler Version: 1.8.0.19 Location: /Applications/Xamarin Profiler.app/Contents/MacOS/Xamarin Profiler Updater Version: 11 Apple Developer Tools Xcode 13.4 (20503) Build 13F17a Xamarin.Mac Version: 8.10.0.1 (Visual Studio Enterprise) Hash: 568bdb24e Branch: d17-2 Build date: 2022-04-08 18:52:56-0400 Xamarin Designer Version: 17.3.0.70 Hash: 4cf1a2208 Branch: remotes/origin/d17-3 Build date: 2022-05-19 16:00:39 UTC Xamarin.iOS Version: 15.10.0.1 (Visual Studio Enterprise) Hash: 568bdb24e Branch: d17-2 Build date: 2022-04-08 18:52:57-0400 Xamarin.Android Version: 12.3.99.58 (Visual Studio Enterprise) Commit: xamarin-android/main/64b22fc Android SDK: /Users/olssonch/Library/Xamarin/android-sdk-macosx Supported Android versions: 12.0 (API level 31) 11.0 (API level 30) SDK Command-line Tools Version: 5.0 SDK Platform Tools Version: 31.0.3 SDK Build Tools Version: 30.0.3 Build Information: Mono: adf1bc4 Java.Interop: xamarin/java.interop/release/6.0.3xx@2a882d2d ProGuard: Guardsquare/proguard/v7.0.1@912d149 SQLite: xamarin/sqlite/3.38.2@7b1e016 Xamarin.Android Tools: xamarin/xamarin-android-tools/main@fc3c2ac Microsoft Build of OpenJDK Java SDK: /Library/Java/JavaVirtualMachines/microsoft-11.jdk 11.0.12 Android Designer EPL code available here: https://github.com/xamarin/AndroidDesigner.EPL Eclipse Temurin JDK Java SDK: /Library/Java/JavaVirtualMachines/temurin-8.jdk 1.8.0.302 Android Designer EPL code available here: https://github.com/xamarin/AndroidDesigner.EPL Android SDK Manager Version: 17.2.0.47 Hash: d6dd135 Branch: remotes/origin/dev/tondat/main-1512059~2 Build date: 2022-05-19 16:00:33 UTC Android Device Manager Version: 0.0.0.1127 Hash: 4bb4d48 Branch: remotes/origin/dev/tondat/main-1536295~1 Build date: 2022-05-19 16:00:33 UTC Build Information Release ID: 1703000191 Git revision: 6e028abf3763bf8fde618c0de2c24b1091eb76d8 Build date: 2022-05-19 15:58:40+00 Build branch: release-17.3 Build lane: release-17.3 Operating System Mac OS X 12.3.1 Darwin 21.4.0 Darwin Kernel Version 21.4.0 Fri Mar 18 00:46:32 PDT 2022 root:xnu-8020.101.4~15/RELEASE_ARM64_T6000 arm64

Build Logs

msbuild.binlog.zip

Example Project (If Possible)

MauiPCSC.zip

chamons commented 2 years ago

Looking at the header files, I don't believe this is a bug, clang is correctly telling you that this library does not work on macatalyst.

You can see that it's a macOS library by doing:

find /Applications/Xcode_13.3.0.app | grep PCSC.tbd

/Applications/Xcode_13.3.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/PCSC.framework/Versions/A/PCSC.tbd
/Applications/Xcode_13.3.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/PCSC.framework/PCSC.tbd

for example.

Also, if you dig into that file, you'll see:

targets:         [ x86_64-macos, arm64-macos, arm64e-macos ]
uuids:
  - target:          x86_64-macos
    value:           DE48AA20-C352-3A0C-9DA1-883F6E56A2FC
  - target:          arm64-macos
    value:           00000000-0000-0000-0000-000000000000
  - target:          arm64e-macos
    value:           40238A1D-06C4-3721-8168-4FD4E8A63976

3 different platforms of macOS, but no catalyst.

I am finding it difficult to find online documentation from Apple to confirm this, as the library appears poorly documented line, but given that I believe net6.0-maccatalyst will not be able to access this library.

chosl commented 2 years ago

Thank you @chamons for the detailed explanation! I'm still trying to put the pieces together and find some logic in the inconsistent behaviour through the maui preview/RC bits. It was fully possible to use the library in the Maui preview 14 bundling but I cannot be certain if it stopped working in RC1 or RC2. I haven't been able to reproduce a successful build with the preview 14 bundle with workload maui since it does't seem to exist in the nuget feeds any longer. Other libraries (pkcs11interop) has also had breaking behaviour during this period but I haven't investigated this further.

Do you have any theory of why it was possible in preview 14 workload (and possibly rc1)?

chamons commented 2 years ago

Hmm, that's really strange that it worked differently in previous previews. Maybe there is some reuse between catalyst and macos libraries that I don't know about, I'm not an expert here.

Let me reopen and have @rolfbjarne review it next week.

rolfbjarne commented 2 years ago

This seems to be a bug in Apple's native linker, this should either work for us, or not work for Xcode projects.

The problem is that the native linker complains when passed -framework PCSC, because it thinks the PCSC framework is only available on macOS. The reason it works when using Xcode, is because the linker receives the -framework PCSS argument in a different manner (as a LC_LINKER_OPTION load argument in the object file, instead of on the command line), and in that case the native linker doesn't complain.

This is the result of compiling a *.m file with Xcode:

#include <PCSC/PCSC.h>

int main(int argc, char * argv[]) {
    SCardIsValidContext (0);
}
$ otool -l /Users/rolf/Library/Developer/Xcode/DerivedData/.../main.o | grep PCSC -B 5
Load command 29
     cmd LC_LINKER_OPTION
 cmdsize 32
   count 2
  string #1 -framework
  string #2 PCSC

Now the question becomes: can we somehow replicate this?

  1. Clang has an --linker-option flag.

Let's try compiling our main.*.mm file with this flag:

$ clang [...]/main.x86_64.mm -Xclang "--linker-option=-framework PCSC"

but unfortunately that doesn't work, because -framework PCSC ends up being a single argument to the linker:

ld: warning: unknown linker option from object file ignored: '-framework PCSC' in [...]/main.x86_64.o

$ otool -l /Users/rolf/Downloads/Maui/obj/Debug/net6.0-maccatalyst/maccatalyst-x64/nativelibraries/main.x86_64.o | grep PCSC -B 4
Load command 4
     cmd LC_LINKER_OPTION
 cmdsize 32
   count 1
  string #1 -framework PCSC

Using passing the two strings as two different arguments doesn't work either:

$ clang [...]/main.x86_64.mm -Xclang --linker-option=-framework -Xclang --linker-option=PCSC"

ld: warning: unknown linker option from object file ignored: '-framework' in [...]/main.x86_64.o ld: warning: unknown linker option from object file ignored: 'PCSC' in [...]/main.x86_64.o

$ otool -l /Users/rolf/Downloads/Maui/obj/Debug/net6.0-maccatalyst/maccatalyst-x64/nativelibraries/main.x86_64.o | grep PCSC -B 4
Load command 5
     cmd LC_LINKER_OPTION
 cmdsize 24
   count 1
  string #1 PCSC

And looking at clang's source code, I couldn't find a way to make clang do the right thing.

  1. Clang supports `#pragma comment(lib "linkWithMe")

Let's add the -fms-extensions flag to the clang commend line, and add this to our main.x86_64.mm file:

#pragma comment(lib, "-framework PCSC")

but that results in:

$ $ otool -l /Users/rolf/Downloads/Maui/obj/Debug/net6.0-maccatalyst/maccatalyst-x64/nativelibraries/main.x86_64.o | grep PCSC -B 4
Load command 4
     cmd LC_LINKER_OPTION
 cmdsize 32
   count 1
  string #1 -l-framework PCSC

clang prepends -l before writing the linker flag in the object file (but even without it it would still be a single argument instead of two arguments).

  1. The last resort would be to generate a file with "#include <PCSC/PCSC.h>", and include that in the build.

This seems to work, with a few notable findings:

[1]: Sidenote: this means that you can't use the PCSC framework from an Objective-C++ file in Xcode, because you run into the same problem this issue is describing.

chosl commented 2 years ago

Thank you @rolfbjarne, the explanation is very impressive and greatly appreciated.

I haven't examined the the PCSC-sharp in great detail but it's a cross-platform library built for netstandard2.0, changing target to net6.0/net6.0-maccatalyst changes nothing though. It's using DllImport and I'm definitely missing a few pieces when it comes to knowledge of the toolchain but...

Am I correct in assuming having .framework in DllImport causes the -framework parameter? Unfortunately I don't have any build logs for the successful build when using maui-preview14 bundle, but could the reason for it working in preview 14 (and possibly RC1) be that the -framework parameter was not passed? The dynamic loading and native methods worked as expected.

I will reach out to danm-de, the owner of PCSC-sharp but I would be very grateful for any possible workarounds.

Relevant parts of PCSC-sharp: https://github.com/danm-de/pcsc-sharp/blob/7a0398f9221c91735150958d09ad83a5be47fe4d/src/PCSC/Interop/MacOSX/MacOsxNativeMethods.cs#L10

private const string PCSC_LIB = "PCSC.framework/PCSC";

[DllImport(PCSC_LIB)] internal static extern int SCardReleaseContext( [In] int hContext);

The PCSC library itself is dynamically loaded with: if (_libHandle == IntPtr.Zero) { _libHandle = dlopen(PCSC_LIB, (int) DLOPEN_FLAGS.RTLD_LAZY);

Note: Referencing the full path, for example /System/Library/Frameworks/PCSC.framework/PCSC doesn't seem to make any difference, '/Applications/Xcode.app/Co....works//PCSC.framework/PCSC.tbd' is still referenced in the errorcode.

rolfbjarne commented 2 years ago

Am I correct in assuming having .framework in DllImport causes the -framework parameter?

Correct:

Linking with the framework PCSC because it's referenced by a module reference in PCSC.dll

I will reach out to danm-de, the owner of PCSC-sharp but I would be very grateful for any possible workarounds.

It looks like adding this to your csproj would work:

<Target Name="RemovePCSC" BeforeTargets="_ComputeLinkNativeExecutableInputs" AfterTargets="_LoadLinkerOutput">
    <ItemGroup>
        <_LinkerFrameworks Remove="PCSC" />
    </ItemGroup>
</Target>
chosl commented 2 years ago

Sweeeeeeet success 🥳, it's working perfectly! Thank you very much for making my day @rolfbjarne

rolfbjarne commented 1 year ago

More info about LC_LINKER_OPTION: https://www.smileykeith.com/2022/02/23/lc-linker-option/

DesmondMilez commented 9 months ago

Hi. I used that workaround in my .NET 7 app. However after I updated in .NET 8 and trying to use that nugget, I am getting:

System.DllNotFoundException: PCSC.framework/PCSC at PCSC.Interop.MacOSX.PCSCliteMacOsX.EstablishContext(SCardScope dwScope, IntPtr pvReserved1, IntPtr pvReserved2, IntPtr& phContext)

Also I noticed that now the app builds even without this workaround:

`

<_LinkerFrameworks Remove="PCSC" />

`

@chosl are you getting the same issue?

chosl commented 9 months ago

@DesmondMilez I too have observed this.. but... I was behind on all version so in order to try .NET 8 I had to upgrade to macOS 14.1.1 and XCode 15.0.1. After getting System.DllNotFoundException: PCSC.framework/PCSC in .NET 8 I first tried to lock the .NET version with global.json file and later uninstalling .NET 8 all together. Surprisingly, I still got the same error. So this seems to happen even in .NET7 when compiled for debug under macOS 14.1.1. As an additional test, I tested a previously compiled (under macOS 13 Microsoft hosted agent in Azure DevOps) and packaged version of our application. This packaged version works well under macOS 14.1.1, it is signed built as a universal app (arm64+x64) so there's also the possibility of arm64, x64 and signing in debug and release mode.

@rolfbjarne I've observed changes in CCID driver handling in macOS 14.0 and 14.1. Ludovic wrote a good post at https://blog.apdu.fr/posts/2023/11/apple-own-ccid-driver-in-sonoma/. Do you have some insight how or if this can be related to the loading of PCSC.framework/PCSC?

Have a great one!

rolfbjarne commented 9 months ago

@rolfbjarne I've observed changes in CCID driver handling in macOS 14.0 and 14.1. Ludovic wrote a good post at blog.apdu.fr/posts/2023/11/apple-own-ccid-driver-in-sonoma. Do you have some insight how or if this can be related to the loading of PCSC.framework/PCSC?

No idea whatsoever, but my guess is that it's unrelated. This bug report describes an issue entirely within the native linker (ld), what the code in the frameworks in question actually do is irrelevant.