admin管理员组

文章数量:1125947

I have a .NET 9 project that gets published as a native AOT library for Windows and Linux. This library exports some methods, that are in turn called from Java via FFM from Windows or Linux again. In general this works pretty well. The native library does some fairly complex operations and I can use it without issues under Windows. Under Linux this works as well except for one error: After the program finishes, I get a segfault from Java. The calls itself seem to work well, I get all the expected results and the calls itself don't seem to cause the error, i.e. the program runs to the end and causes the segfault afterwards.

I can reproduce this under a dedicated Debian 12 installation and with a WSL2 Debian distro.

I have reduced this to an absolute minimal example without any logic that still reproduces that:

using System.Runtime.InteropServices;

namespace NativeTest
{
    public class Class1
    {
        [UnmanagedCallersOnly(EntryPoint = "BasicTest")]
        public static void BasicTest()
        {
            Console.WriteLine("Hello from native");
        }
    }
}

csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsTrimmable>True</IsTrimmable>
    <IsAotCompatible>True</IsAotCompatible>
    <PublishAot>True</PublishAot>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

</Project>

Java:

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class Main {   
   
    public static void main(String[] args) {
        try (Arena arena = Arena.ofConfined()) {
            Linker linker = Linker.nativeLinker();        
            SymbolLookup library = SymbolLookup.libraryLookup("./NativeTest.so", arena);
            
            MethodHandle myMethod = linker.downcallHandle(library.find("BasicTest").orElseThrow(), FunctionDescriptor.ofVoid());
          
            myMethod.invoke();
            
        } catch (Throwable t) {
            System.out.println("Error is " + t.getMessage());
        }
    }
}

Executed using:

dotnet publish -r linux-x64 -c Debug
javac Main.java
java --enable-native-access=ALL-UNNAMED Main

Results in:

Hello from native
[1]    815 segmentation fault (core dumped)  java --enable-native-access=ALL-UNNAMED Main

Versions:

# java --version
java 23.0.1 2024-10-15
Java(TM) SE Runtime Environment (build 23.0.1+11-39)
Java HotSpot(TM) 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)
# dotnet --version
9.0.101

What am I missing here?

I have a .NET 9 project that gets published as a native AOT library for Windows and Linux. This library exports some methods, that are in turn called from Java via FFM from Windows or Linux again. In general this works pretty well. The native library does some fairly complex operations and I can use it without issues under Windows. Under Linux this works as well except for one error: After the program finishes, I get a segfault from Java. The calls itself seem to work well, I get all the expected results and the calls itself don't seem to cause the error, i.e. the program runs to the end and causes the segfault afterwards.

I can reproduce this under a dedicated Debian 12 installation and with a WSL2 Debian distro.

I have reduced this to an absolute minimal example without any logic that still reproduces that:

using System.Runtime.InteropServices;

namespace NativeTest
{
    public class Class1
    {
        [UnmanagedCallersOnly(EntryPoint = "BasicTest")]
        public static void BasicTest()
        {
            Console.WriteLine("Hello from native");
        }
    }
}

csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <IsTrimmable>True</IsTrimmable>
    <IsAotCompatible>True</IsAotCompatible>
    <PublishAot>True</PublishAot>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

</Project>

Java:

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class Main {   
   
    public static void main(String[] args) {
        try (Arena arena = Arena.ofConfined()) {
            Linker linker = Linker.nativeLinker();        
            SymbolLookup library = SymbolLookup.libraryLookup("./NativeTest.so", arena);
            
            MethodHandle myMethod = linker.downcallHandle(library.find("BasicTest").orElseThrow(), FunctionDescriptor.ofVoid());
          
            myMethod.invoke();
            
        } catch (Throwable t) {
            System.out.println("Error is " + t.getMessage());
        }
    }
}

Executed using:

dotnet publish -r linux-x64 -c Debug
javac Main.java
java --enable-native-access=ALL-UNNAMED Main

Results in:

Hello from native
[1]    815 segmentation fault (core dumped)  java --enable-native-access=ALL-UNNAMED Main

Versions:

# java --version
java 23.0.1 2024-10-15
Java(TM) SE Runtime Environment (build 23.0.1+11-39)
Java HotSpot(TM) 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)
# dotnet --version
9.0.101

What am I missing here?

Share Improve this question asked 2 days ago LennartLennart 10.3k16 gold badges70 silver badges95 bronze badges 2
  • The AOT Lib, is it 3rd Party or do you have complete control over all the code? – Fildor Commented 2 days ago
  • 1 I have full control over all C# and Java code. – Lennart Commented 2 days ago
Add a comment  | 

1 Answer 1

Reset to default 1

It seems that the issue was the type of Arena (Arena.ofConfined()). If I use an automatic arena (Arena.ofAuto()), I don't get a segfault:

public static void main(String[] args) throws Throwable {
        Arena arena = Arena.ofAuto();
        Linker linker = Linker.nativeLinker();        
        SymbolLookup library = SymbolLookup.libraryLookup("./NativeTest.so", arena);
        
        MethodHandle myMethod = linker.downcallHandle(library.find("BasicTest").orElseThrow(), FunctionDescriptor.ofVoid());        
        myMethod.invoke();
}

The difference seems to be that a confined arena has a specifc single owner thread, and that automatic arenas are managed by the garbage collector (see Docs).

Since that doesn't really matter in my case (I initially just used the samples from the docs), I'll go with an automatic arena.

本文标签: