dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.98k stars 4.66k forks source link

Dependency on System.Runtime.InteropServices.RuntimeInformation causes runtime error for apps targeting net451 on *nix systems (i.e. running on Mono) #17471

Closed JunTaoLuo closed 4 years ago

JunTaoLuo commented 8 years ago

Scenario

1) Running a net451 app depending on System.Runtime.InteropServices.RuntimeInformation on OSX/Ubuntu will produce the following exception at runtime:

 dotnet -v run -f net451                                                                                                                                                                               1 ↵
Telemetry is: Enabled
Project RuntimeServiceOnMono (.NETFramework,Version=v4.5.1) was previously compiled. Skipping compilation.
Running /usr/local/bin/mono --debug /Users/jtluo/Documents/workspace/juntaoluo/tp/RuntimeServiceOnMono/bin/Debug/net451/osx.10.11-x64/RuntimeServiceOnMono.exe
Process ID: 29130

Unhandled Exception:
System.DllNotFoundException: System.Native
  at (wrapper managed-to-native) Interop/Sys:GetUnixNamePrivate ()
  at Interop+Sys.GetUnixName () [0x00000] in <filename unknown>:0
  at System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (OSPlatform osPlatform) [0x00000] in <filename unknown>:0
  at ConsoleApplication.Program.Main (System.String[] args) [0x00000] in <filename unknown>:0
[ERROR] FATAL UNHANDLED EXCEPTION: System.DllNotFoundException: System.Native
  at (wrapper managed-to-native) Interop/Sys:GetUnixNamePrivate ()
  at Interop+Sys.GetUnixName () [0x00000] in <filename unknown>:0
  at System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (OSPlatform osPlatform) [0x00000] in <filename unknown>:0
  at ConsoleApplication.Program.Main (System.String[] args) [0x00000] in <filename unknown>:0

Example

See repro at https://github.com/JunTaoLuo/RuntimeServiceOnMono. To run the sample, run dotnet restore and dotnet run -f net451

I understand Mono is not a scenario that's actively being developed for but we should not be causing a exception like this especially since we intend to use the InteropServices APIs in often used ASP.NET packages like Logging.

Currently this means that most of our samples and apps cannot run on Mono (full CLR on *nix).

Environment

dotnet --version:

Microsoft .NET Core Shared Framework Host

  Version  : 1.0.1-rc3-004312-00
  Build    : 1db6c07638a70a621b312e78d4dc9fb31a530f2f

mono -V:

Mono JIT compiler version 4.0.5 ((detached/1d8d582 Tue Oct 20 15:15:33 EDT 2015)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
    TLS:           normal
    SIGSEGV:       altstack
    Notification:  kqueue
    Architecture:  x86
    Disabled:      none
    Misc:          softdebug
    LLVM:          yes(3.6.0svn-mono-(detached/a173357)
    GC:            sgen

cc @BrennanConroy @muratg @Eilon

joshfree commented 8 years ago

Version : 1.0.1-rc3-004312-00

@JunTaoLuo could you upgrade to CoreFX rc3-24128-00 / Microsoft.NetCore.App 1.0.0-rc3-004338 / CLI 1.0.0-preview2-002911 and report back?

mellinoe commented 8 years ago

I suspect this will still be happening on the latest packages. BTW, it looks like the issue is with System.Runtime.InteropServices.RuntimeInformation, rather than System.Runtime.InteropServices. We have a net45 implementation for Windows, but not for Unix. Unix probably always picks up the netstandard1.1 version, which won't necessarily work on Mono. I guess it would work if System.Native was deployed with the app, but that probably won't happen by default.

muratg commented 8 years ago

cc @Eilon @DamianEdwards @davidfowl

ericstj commented 8 years ago

Updated the title and assignee. This isn't for Interop, it's RuntimeInformation. I would expect some different results using the latest packages, since those will probably result in the desktop implementation being chosen.

Not much we can do in packaging here since Mono pretends to be desktop and doesn't use a RID.

We could make the desktop library try to p-inoke to some *nix APIs (directly, without shim) if it didn't find the Windows ones. Not sure how folks feel about that.

JunTaoLuo commented 8 years ago

I still see the same error with the latest packages. System.Runtime.InteropServices.RuntimeInformation/4.0.0-rc3-24128-00. CLI version: 1.0.0-preview2-002913. Note that the repro only targets net451 and does not depend on Microsoft.NetCore.App.

joshfree commented 8 years ago

@ellismg is going to take a look

ellismg commented 8 years ago

https://github.com/dotnet/cli/issues/3350 is a CLI problem that is causing a unix asset to be deployed instead of the Windows one. Even if we deploy the correct asset, we still end up with p/invokes into kernel32 (which won't work) but we'd at least be able to provide some light-up mono implementation via late-binding.

sunsided commented 8 years ago

btw., same on mono 4.5.2.372 on Ubuntu 14.04, targeting net452 with 1.0.0 packages from NuGet. I see it on the console logger though (.AddConsole()).

Is there some workaround we can use?

borgdylan commented 8 years ago

I confirm that by using a separately built System.Native, I could get the RuntimeInformation class to work on Mono on both x86 and x64 Ubuntu 16.04. It is a matter of shipping those libraries as source/binaries for the architectures that mono supports.

borgdylan commented 8 years ago

@ericstj The .NET CLI includes a RuntimeInformation class that detects Windows vs. *nix cases and P/Invokes straight thru to the native syscalls. I confirm that, that code cross-compiles and works fine unmodified on Linux/Mono: https://github.com/dotnet/cli/tree/rel/1.0.0/src/Microsoft.DotNet.InternalAbstractions/Native

ericstj commented 8 years ago

@borgdylan that's already understood. As @ellismg mentioned we need to decide how we want to do that since mono represents itself as .NET desktop WRT to NuGet.

borgdylan commented 8 years ago

The way the CLI internal abstractions work would be best. That code works ok irrespective of CLR and treats operating systems directly. The alternative would be to make mono use parts of System.Native and friends as part of its codebase so that APIs new in corefx and not present in mono would just work. That would open up prospects of using mono's core parts i.e its CLR as a CoreCLR stand-in on platforms where the CoreCLR does not run in order to run apps that target corefx. Example platforms are Linux x86, LInux on MIPS, Linux on PowerPC.

borgdylan commented 8 years ago

The latter case would need cooperation from the Xamarin team.

ericstj commented 8 years ago

code works ok irrespective of CLR

Yep that's what we're thinking to do for this particular case. We'll make the desktop implementation of this contract "work" on Mono and give some meaningful results without relying on p-invokes to a native shim we cannot deploy. I suspect we'll do some reflection lightup to detect it's mono then call some mono APIs that provide similar information. That's what @ellismg meant by late binding.

I don't think we want to have the native shims become something that's part of Mono. They are too brittle.

borgdylan commented 8 years ago

@ericstj The reflection based search for Mono.Runtime where finding it means Mono and not finding it means .NET CLR has been done for years without issues. I consider this as the Litmus test for Mono.

ericstj commented 8 years ago

Yep, the other thing we need is an implementation of this for Mono, ideally without any PInvokes. Perhaps we can translate mono's Environment.OSVersion if that's reliable.

borgdylan commented 8 years ago

First of all due to how full framework behaves, Mono will not distinguish between Linux and Mac in the Environment APIs. Both will be reported as Unix.

borgdylan commented 8 years ago

A per before, you can distinguish 32 and 64 bit but not whether it is ARM/Intel/MIPS/PowerPC.

borgdylan commented 8 years ago

The OS version will be the Linux kernel version. (in the Linux case)

borgdylan commented 8 years ago

Therefore this cannot be done using pure Mono/.NET without P/Invokes. There is a reason why System.Native was used for CoreCLR. The only way to make this API run perfect is to have native implementation libraries on all three CLRs or to have all three CLRs ship a runtime info file or use an RID.

borgdylan commented 8 years ago

The code in: https://github.com/dotnet/cli/blob/rel/1.0.0/src/Microsoft.DotNet.InternalAbstractions/Native/PlatformApis.cs solves part of the problem but requires IO and P/Invokes.

borgdylan commented 8 years ago

The code in: https://github.com/dotnet/cli/blob/rel/1.0.0/src/Microsoft.DotNet.InternalAbstractions/RuntimeEnvironment.cs reiterates the fact that you cannot distinguish Intel/ARM/MIPS/PowerPc. Mono supports a host of architectures and you cannot get it right without the System.Native port to all those architectures.

borgdylan commented 8 years ago

@ericstj Do remember that Mono means Xamarin which means MSFT. I do see a world where .NET and mono teams collaborate to have a subset of System.Native be present in Mono and be compatible with the CoreCLR version.

ericstj commented 8 years ago

Do remember that Mono means Xamarin which means MSFT.

@borgdylan I'm not arguing here, nor do I disagree with anything that's been said around mono support. I am sorry if that's how this is coming off, but that's the last thing on my mind. I want to support mono and I want to do so with minimal coupling. I'm just stating the constraints we have and discussing possible design choices.

you cannot get it right without the System.Native port to all those architectures.

Shims don't solve the processor architecture problem, they just push it to NuGet. The shims hardcode the architecture value and NuGet uses RID to give us the shim with the right hardcoded value. I'd like to keep the goal of not having mono projects require a RID.

Which I understand is why you are suggesting:

System.Native be present in Mono

/cc @stephentoub @jkotas @davidfowl The shims are not public surface area. They version quite frequently and are required to do so in order to support new API. If we put them in mono it means mono pins itself to a specific shim version that must be target-able by upstack packages. We don't have any way to represent that today. It means we'd have to 'know' that net46 means a mono version with shim version X and if a new implementation required version X+1 of that shim it couldn't be supported on net46 due to mono's pinning, even though that has nothing to do with desktop framework. I really don't want to go down this rabbit hole if it can be avoided.

The main reason we have shims was to avoid many forks of the IL per-distro/version, initially we were doing all the translation in IL and directly PInvoking to the nix OS. I suspect there is a way to do so here, we just need someone more knowledgeable in nix than myself to do a deep dive. I think @ellismg already has an idea here so I'd like to wait for his feedback, especially since this issue is assigned to him, before we go further down the path of trying to design this.

borgdylan commented 8 years ago

Ok. I just cannot understand how you would get the actual architecture without Mono and .NET having a way to tell that in the first place. In fact the problem arises from the fact that any CLR is there to abstract the CPU architecture from the IL code which is architecture independent. This is what the S.R.IS.RI API is for and therefore this is a chicken and egg type problem.

ericstj commented 8 years ago

We could do something like we do on Windows where we make a platform call to do it. Just need to find the right method that isn't too distro specific. Perhaps reading /proc/cpuinfo is enough.

borgdylan commented 8 years ago

That only lists each CPU core and its capabilities. It makes no mention of the architecture. Calling uname -a will yield a better result.

dylan@ubuntu-server:~$ uname -a
Linux ubuntu-server 4.4.0-28-generic dotnet/corefx#47-Ubuntu SMP Fri Jun 24 10:09:13 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

On a Mac, LInux becomes Darwin and the architecture constant may be different. For Linux x86_64 means x64, i386 and i686 mean x86 and armv7l means ARM. I never saw the architecture constant for Aarch64.

borgdylan commented 8 years ago

Some docs: http://linux.die.net/man/1/uname

borgdylan commented 8 years ago

@JunTaoLuo The issue does not only appear when using Logging but also when using Kestrel in general if you target RTM.

Tragetaschen commented 8 years ago

Does uname handle / show the difference between hard-float and soft-float on ARM?

borgdylan commented 8 years ago

It should. amrv7l means hard float. Soft float may be shown as armv5.

borgdylan commented 8 years ago

This: https://wiki.debian.org/Multiarch/Tuples says that I am wrong on my latest comment. They will both show up as arm. Since the hard/soft float issue is serious we need another command that can distinguish between them.

Tragetaschen commented 8 years ago

Usually you have a compiler tool chain that has built your Linux environment and that emits either hard float or soft float code. So in a .NET world: couldn't you ask the JIT compiler what it's emitting?

borgdylan commented 8 years ago

uname shows OS i.e. Linux/Darwin and the architecture (but only distinguishes between 32 and 64 bit ARM and ignores hard float). lscpu shows just the CPU stuff and quotes teh architecture like uname does.

dylan@ubuntu-server:~$ lscpu
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                16
On-line CPU(s) list:   0-15
Thread(s) per core:    2
Core(s) per socket:    8
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Core(TM) i7-6900K CPU @ 3.20GHz
Stepping:              1
CPU MHz:               1200.000
CPU max MHz:           3200.0000
CPU min MHz:           1200.0000
BogoMIPS:              8000.40
Virtualisation:        VT-x
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              20480K
NUMA node0 CPU(s):     0-15
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch epb intel_pt tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm cqm rdseed adx smap xsaveopt cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts

It is therefore not a viable candidate.

borgdylan commented 8 years ago

The internet gave me an answer. If you are on ARM the litmus test is to list all directories under /lib and see if you have arm-linux-gnueabihf or arm-linux-gnueabi under it.

Tragetaschen commented 8 years ago

I will check my platform tomorrow (a "standard" Yocto build) when I have access to the hardware

jkotas commented 8 years ago

@marek-safar Do you opinions on the best way to make https://github.com/dotnet/corefx/tree/master/src/System.Runtime.InteropServices.RuntimeInformation work on Mono?

Tragetaschen commented 8 years ago

@borgdylan I have checked my embedded system and there isn't any of the two mentioned folders. Might be Debian specific.

# find /lib -maxdepth 1 -type d
/lib
/lib/systemd
/lib/modprobe.d
/lib/depmod.d
/lib/udev
/lib/modules

However, I do have a /lib/ld-linux-armhf.so.3 file.

borgdylan commented 8 years ago

On x86 I have ld-linux.so.2. I will check later on my pc which is x64 based but has x86 compat libraries as well. Looks like ARM will require different tricks to spot the architecture precisely.

borgdylan commented 8 years ago

On systems that have gcc installed one can use the the Target: line when invoking:

dylan@asusx550ca:~$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/5/lto-wrapper
Target: i686-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.3.1-14ubuntu2.1' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-i386/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-i386 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-i386 --with-arch-directory=i386 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-targets=all --enable-multiarch --disable-werror --with-arch-32=i686 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
Thread model: posix
gcc version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2.1) 
borgdylan commented 8 years ago

A similar line is output when running clang -v. Using the clang variant may work better on macs since clang is an apple product and is used by apple;s SDKs.

Tragetaschen commented 8 years ago

There is only a single C function required from System.Native:

https://github.com/Tragetaschen/libSystem.Native/blob/master/src/GetUnixName.c

And I have wrapped that in an autotools project.

borgdylan commented 8 years ago

The API returns more than that. Also it supports getting the architecture. Your solution makes things work but does not yield the expected result.

borgdylan commented 8 years ago

Instead I'd wrap the actual code from corefx.

Tragetaschen commented 8 years ago

The "code" from corefx is that same C (++) function that instead contains a preprocessor macro whose value comes from the cmake environment. As long as you try to find a solution for Mono on Linux, my code just skips all that boilerplate.

borgdylan commented 8 years ago

Corefx calls uname to get a full version string. Any implementation that is to be used in general would need to allow all of the S.R.IS.RI API to work.

borgdylan commented 8 years ago

@jkotas can we use the code from corefx to make a reduced version of the library while leaving copyrights and licenses as those of corefx? The short term solution would be to have a few C++ files and a make file to build and install the library.

borgdylan commented 8 years ago

I';d use the builtin gcc macros: https://sourceforge.net/p/predef/wiki/Architectures/

jkotas commented 8 years ago

@borgdylan The license describes how you can use this code.

Why you cannot use the System.Native library as is? I understand that it has more stuff in it that what you need to solve your immediate problem, but the extra footprint of this extra stuff should be negligible.

Tragetaschen commented 8 years ago

@jkotas For me, the entry barrier was really, really high: the immediate problem was the requirement for a llvm/cmake toolchain that targets (my) ARM device.

cmake is required to get the necessary configuration header files (pal_config.h). llvm is required since gcc throws a sorry, unimplemented: non-trivial designated initializers not supported for constructs like this.