在 macOS 中从 Java 获取 errno 的原生值

问题描述 投票:0回答:3

我正在使用 JDK 19 中的外部函数和内存 API (JEP 424) 将基于 JNA 的库移植到“纯”Java。

我已经成功实现了

sysctl()
函数并使用以下方式获取值:

public static MethodHandle sysctl = Linker.nativeLinker().downcallHandle(
        SYSTEM_LIBRARY.lookup("sysctl").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT,
                ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS,
                ValueLayout.ADDRESS, ValueLayout.JAVA_LONG));

在实现此功能之前的调试过程中,我偶尔会得到 -1 返回值:

成功完成后,返回值0;否则返回值-1,并设置全局变量 errno 来指示错误。

在 JNA 中,可以使用

Native.getLastError()
轻松获得。 这个答案展示了我如何在 JNI 中编写代码来获取它,但 JEP 424 的重点是消除对 JNA 或 JNI 的需要。

Windows API 有一个有用的 GetLastError 函数可以映射,如 JDK 中的测试

所示

但是,我无法在 macOS 上找到任何可以映射以返回的本机函数

errno
。我发现的最接近的是
perror()
它将错误打印到 STDERR:

public static MethodHandle perror = Linker.nativeLinker()
        .downcallHandle(SYSTEM_LIBRARY.lookup("perror").orElseThrow(),
                FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS));

虽然输出到 STDERR 在短期内很有帮助,但我想使用该值进行异常处理/错误消息。

这似乎是一个足够常见的用例,JDK 中应该有一个方法,但我找不到它。

除了暂时将 STDERR 重定向到我可以解析的字符串之外,还有什么想法吗?

java macos native errno project-panama
3个回答
3
投票

我无法使用Mac来测试这个,但如果这个来源是准确的:https://opensource.apple.com/source/xnu/xnu-4570.41.2/bsd/sys/errno.h。 auto.html

那么

errno
是一个宏,定义如下:

__BEGIN_DECLS
extern int * __error(void);
#define errno (*__error())
__END_DECLS

这是对函数的调用,该函数返回指向

int*
值的
errno

因此,要从 Java 访问它应该可行:

static final MethodHandle __error = Linker.nativeLinker()
    .downcallHandle(SYSTEM_LIBRARY.lookup("__error").orElseThrow(),
            FunctionDescriptor.of(ValueLayout.ADDRESS));

static int errno() {
    try {
        MemoryAddress addr = (MemoryAddress) __error.invokeExact();
        return addr.get(ValueLayout.JAVA_INT, 0);
    } catch (Throwable t) {
        throw new RuntimeException(t);
    }
}

请注意,目前(JDK 19)这并不是 100% 可靠的访问

errno
的方式,因为虚拟机可能会阻塞某处并在读取值之前用自己的系统调用覆盖
errno

我们一直在讨论如何更可靠地访问

errno
以及虚拟机可以覆盖的其他线程局部变量。同时,这也应该使访问该值变得更加容易。


1
投票

对于 JDK 21+ 有一个推荐的替代方案,在 Linker 类的文档中提到

try (var confinedArena = Arena.ofConfined()) {

    // SYSTEM_LIBRARY doesn't exist anymore
    Function<String, MemorySegment> lookup = symbol ->
            Linker.nativeLinker().defaultLookup().find(symbol)
                    .or(() -> SymbolLookup.loaderLookup().find(symbol)).orElseThrow();

    // declare the errno as state to be captured, directly after the downcall without any interence of the
    // JVM runtime
    StructLayout capturedStateLayout = Linker.Option.captureStateLayout();
    VarHandle errnoHandle = capturedStateLayout.varHandle(MemoryLayout.PathElement.groupElement("errno"));
    Linker.Option ccs = Linker.Option.captureCallState("errno");

    MethodHandle sysctl = Linker.nativeLinker().downcallHandle(
            lookup.apply("sysctl"), FunctionDescriptor.of(ValueLayout.JAVA_INT,
                    ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS,
                    ValueLayout.ADDRESS, ValueLayout.JAVA_LONG), ccs);

    MemorySegment capturedState = confinedArena.allocate(capturedStateLayout);
    try {
        int result = (int) sysctl.invoke(capturedState, ...);
        int errno = (int) errnoHandle.get(capturedState);
    } catch (Throwable e) {
        throw new RuntimeException(e);
    }
}

要获取错误字符串,可以调用 strerror 函数:

String errnoString(int errno){
    AddressLayout POINTER = ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(JAVA_BYTE));
    MethodHandle strerror = Linker.nativeLinker()
            .downcallHandle(lookup("strerror"),
                    FunctionDescriptor.of(POINTER, ValueLayout.JAVA_INT));
    try {
        MemorySegment str = (MemorySegment) strerror.invokeExact(errno);
        System.out.println(str);
        return str.getUtf8String(0);
    } catch (Throwable t) {
        throw new RuntimeException(t);
    }
}

0
投票

啊,你找的函数叫

strerror
,它可以将错误代码(
errno
)映射到字符串。

基于 Apple 文档。错误消息在

errno
变量中设置:

当系统调用检测到错误时,它返回一个整数值来指示 ing 失败(通常为 -1)并相应地设置变量 errno。 成功的调用永远不会设置 errno;一旦设置,它仍然存在 直到另一个错误发生。仅应在出现错误后进行检查。

#include <string.h> //header for strerror
#include <stdio.h>
#include <sys/errno.h> //Header for error enumerations.

int main(int argc, const char * argv[]) {

  //taking "EPERM" as an errno example
  int eperm = EPERM;

  char * errStr = strerror(eperm);

  printf("%s\n", errStr);//prints 'Operation not permitted'

  return 0;
}

所以尝试

strerror
errno
转换为错误消息字符串

最新问题
© www.soinside.com 2019 - 2024. All rights reserved.