gary-rowe / hid4java

A cross-platform Java Native Access (JNA) wrapper for the libusb/hidapi library. Works out of the box on Windows/Mac/Linux.
MIT License
229 stars 71 forks source link

Error SIGSEGV (0xb) at attaching/detaching usb devices #104

Closed Laivindur closed 3 years ago

Laivindur commented 3 years ago

First of all, I'm unsure about how to report this bug and if it's really related to hid4java. The reasons for me to believe it are founded in some tests, core dumps analysis and different trials but, despite all the years working with Java, I have to admit that errors at such low level are very cryptic to me and I don't have a deep understanding of the JVM internals.

Bug Description In runtime, when any USB device is connected, the JVM crashes due to SIGSEGV

#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f0076fa09fd, pid=7111, tid=0x00007eff5eeb1700
#
# JRE version: OpenJDK Runtime Environment (8.0_265-b01) (build 1.8.0_265-8u265-b01-0ubuntu2~18.04-b01)
# Java VM: OpenJDK 64-Bit Server VM (25.265-b01 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  [libc.so.6+0x979fd]  cfree+0x3d
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# If you would like to submit a bug report, please visit:
#   http://bugreport.java.com/bugreport/crash.jsp
#

---------------  T H R E A D  ---------------

Current thread is native thread

siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000001

It happens in runtime when the app (Spring Boot app) already booted. Hid4JavaService is configured and started. By this time

This error never happens when hid4java is the only lib handling and managing USB devices. We have realised that the problem arises when it works alongside with usb4java.

How do we know? We realised that, if we disable hid4java service initialization or usb4java initialization in the production code, the USB events are handled by the remaining API with no errors. The JVM doesn't crash.

When both are up, there's a conflict at handling the USB events. Perhaps a race condition between both threads (service workers).

To Reproduce It's hard to say because it's happening in a very complex application, which is multi-thread and on top of this, there's SpringBoot. However, I have reproduced a very similar one, isolating both libs from Spring and the rest of the application.

For brevity, I will share my pom.xml for you to know what is involved and the respective versions of the libs.

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.opentrends.abaxis</groupId>
    <artifactId>lab.ua</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>lab.ua</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
        <junit.jupiter.version>5.6.2</junit.jupiter.version>
    </properties>

    <dependencies>
        <!-- USB4JAVA - http://usb4java.org/ -->
        <!-- Based on http://javax-usb.sourceforge.net/ -->
        <dependency>
            <groupId>org.usb4java</groupId>
            <artifactId>usb4java-javax</artifactId>
            <version>1.3.0</version>
        </dependency>

        <!-- JSCC - https://code.google.com/archive/p/java-simple-serial-connector/ -->
        <dependency>
            <groupId>org.scream3r</groupId>
            <artifactId>jssc</artifactId>
            <version>2.8.0</version>
        </dependency>

        <dependency>
            <groupId>org.hid4java</groupId>
            <artifactId>hid4java</artifactId>
            <!-- <version>0.5.0</version> -->   <!-- Funciona -->
            <version>0.7.0</version>    <!-- No funciona -->
            <!-- <version>issue-97</version> --> <!-- Funciona -->
            <!-- <version>issue-99</version> -->
        </dependency>

        <!-- Apache Commons -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.11.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.11.2</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-yaml</artifactId>
            <version>2.9.8</version>
        </dependency>

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.8</version>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <!-- Remember to update the version in default.properties as well -->
            <version>1.9</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit.jupiter.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-library</artifactId>
            <version>2.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>lab-ua-hid-${project.version}</finalName>

        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>2.2</version>
            </plugin>
        </plugins>
    </build>
</project>

The Testing code

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2020 Gary Rowe
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package net.opentrends.abaxis.lab.ua.usb;

import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.usb.UsbDevice;
import javax.usb.UsbDeviceDescriptor;
import javax.usb.UsbException;
import javax.usb.UsbHostManager;
import javax.usb.UsbHub;
import javax.usb.event.UsbServicesEvent;
import javax.usb.event.UsbServicesListener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hid4java.HidDevice;
import org.hid4java.HidException;
import org.hid4java.HidManager;
import org.hid4java.HidServices;
import org.hid4java.HidServicesListener;
import org.hid4java.HidServicesSpecification;
import org.hid4java.event.HidServicesEvent;
import org.usb4java.javax.Services;

import com.sun.jna.Platform;

/**
 * <p>
 * Demonstrate the USB HID interface using a Satoshi Labs Trezor
 * </p>
 *
 * @since 0.0.1
 *
 */
public class UsbEnumerationExample implements HidServicesListener {

    private static final Logger log = LogManager.getLogger(UsbEnumerationExample.class);

    public static void main(String[] args) throws HidException, SecurityException, UsbException {

        UsbEnumerationExample example = new UsbEnumerationExample();
        example.executeExample();

    }

    private void executeExample() throws HidException, SecurityException, UsbException {

        Services service = (org.usb4java.javax.Services) UsbHostManager.getUsbServices();
        service.addUsbServicesListener(new UsbServicesListener() {

            @Override
            public void usbDeviceDetached(UsbServicesEvent event) {
                log.info("Device Detached: {}",event.getUsbDevice());
            }

            @Override
            public void usbDeviceAttached(UsbServicesEvent event) {
                log.info("Device Attached: {}",event.getUsbDevice());

            }
        });
        assertNotNull(service);
        //printHub(service);

        printPlatform();

        // Configure to use custom specification
        HidServicesSpecification hidServicesSpecification = new HidServicesSpecification();
        // Use the v0.7.0 manual start feature to get immediate attach events
        hidServicesSpecification.setAutoStart(false);

        // Get HID services using custom specification
        HidServices hidServices = HidManager.getHidServices(hidServicesSpecification);
        hidServices.addHidServicesListener(this);

        // Manually start the services to get attachment event
        System.out.println(ANSI_GREEN + "Manually starting HID services." + ANSI_RESET);
        hidServices.start();

        System.out.println(ANSI_GREEN + "Enumerating attached devices..." + ANSI_RESET);

        // Provide a list of attached devices
        //for (HidDevice hidDevice : hidServices.getAttachedHidDevices()) {
        //  System.out.println(hidDevice);
        //}

        waitAndShutdown(hidServices);

        //waitAndShutdown(service);

    }

    public void waitAndShutdown(HidServices hidServices) {

        System.out.printf(ANSI_GREEN
                + "Waiting 30s to demonstrate attach/detach handling. Watch for slow response after write if configured.%n"
                + ANSI_RESET);

        // Stop the main thread to demonstrate attach and detach events
        sleepNoInterruption();

        // Shut down and rely on auto-shutdown hook to clear HidApi resources
        hidServices.shutdown();
    }

    private static void printHub(Services service) {
        service.scan();
        UsbHub rootUsbHub = service.getRootUsbHub();
        log.info("UsbHub: {}", rootUsbHub);
        printHubDevices(rootUsbHub);
    }

    @SuppressWarnings("unchecked")
    private static UsbDevice printHubDevices(UsbHub hub) {
        for (UsbDevice device : (List<UsbDevice>) hub.getAttachedUsbDevices()) {
            UsbDeviceDescriptor desc = device.getUsbDeviceDescriptor();
            log.debug("Device: {}", device);
            log.debug("Descriptor: {}", desc);
            if (device.isUsbHub()) {
                device = printHubDevices((UsbHub) device);
                if (device != null) {
                    return device;
                }
            }
        }
        return null;
    }

    public void waitAndShutdown(Services service) {

        System.out.printf(ANSI_GREEN
                + "Waiting 30s to demonstrate attach/detach handling. Watch for slow response after write if configured.%n"
                + ANSI_RESET);

        // Stop the main thread to demonstrate attach and detach events
        sleepNoInterruption();

        // Shut down and rely on auto-shutdown hook to clear HidApi resoure
    }

    /**
     * Invokes {@code unit.}{@link TimeUnit#sleep(long) sleep(sleepFor)}
     * uninterruptibly.
     */
    public static void sleepNoInterruption() {
        boolean interrupted = false;
        try {
            long remainingNanos = TimeUnit.SECONDS.toNanos(30);
            long end = System.nanoTime() + remainingNanos;
            while (true) {
                try {
                    // TimeUnit.sleep() treats negative timeouts just like zero.
                    NANOSECONDS.sleep(remainingNanos);
                    return;
                } catch (InterruptedException e) {
                    interrupted = true;
                    remainingNanos = end - System.nanoTime();
                }
            }
        } finally {
            if (interrupted) {
                Thread.currentThread().interrupt();
            }
        }
    }

    @Override
    public void hidDeviceAttached(HidServicesEvent event) {

        System.out.println(ANSI_BLUE + "Device attached: " + event + ANSI_RESET);

    }

    @Override
    public void hidDeviceDetached(HidServicesEvent event) {

        System.out.println(ANSI_YELLOW + "Device detached: " + event + ANSI_RESET);

    }

    @Override
    public void hidFailure(HidServicesEvent event) {

        System.out.println(ANSI_RED + "HID failure: " + event + ANSI_RESET);

    }

    public void printPlatform() {

        // System info to assist with library detection
        System.out.println("Platform architecture: " + Platform.ARCH);
        System.out.println("Resource prefix: " + Platform.RESOURCE_PREFIX);
        System.out.println("Libusb activation: " + Platform.isLinux());

    }

    public static final String ANSI_RESET = "\u001B[0m";
    public static final String ANSI_BLACK = "\u001B[30m";
    public static final String ANSI_RED = "\u001B[31m";
    public static final String ANSI_GREEN = "\u001B[32m";
    public static final String ANSI_YELLOW = "\u001B[33m";
    public static final String ANSI_BLUE = "\u001B[34m";
    public static final String ANSI_PURPLE = "\u001B[35m";
    public static final String ANSI_CYAN = "\u001B[36m";
    public static final String ANSI_WHITE = "\u001B[37m";
}

As you see, it's a copy of your original UsbEnumerationExample i have just added the code to initialize usb4java. During the waiting time, if we plug any USB device, f(or example, I have used optical mice) it crashes

Assertion 'device' failed at ../src/libsystemd/sd-device/sd-device.c:817, function sd_device_get_devtype(). Aborting.

Desktop (please complete the following information):

Additional context Add any other context about the problem here.

gary-rowe commented 3 years ago

Usually a SIGSEV 0xb error is associated with a low level driver conflict between the JVM and supporting native libraries.

Given

We realised that, if we disable hid4java service initialization or usb4java initialization in the production code, the USB events are handled by the remaining API with no errors. The JVM doesn't crash.

When both are up, there's a conflict at handling the USB events. Perhaps a race condition between both threads (service workers).

I'm inclined to think in this case that it's hidapi and libusb attempting to gain control over the USB interface and conflicting with each other. The question how to fix it.

One approach is to minimise the interaction. Perhaps having usb4java handling attach/detach events and then hid4java only opening specific devices that are classed as HID. You can turn off attach/detach event polling using ScanMode.NO_SCAN in the HidSpecification.

If a deeper dig is required, you probably want to review this answer on StackOverflow.

gary-rowe commented 3 years ago

Any further info on this issue?

Laivindur commented 3 years ago

Hi Gary.

Yes, but I'm afraid of not to be capable of giving a detailed explanation of the fix.

We assumed what you suggested. Both native libs were competing since both were handling the USB attachments and detachments events. Maybe it was a lock, usb4java locking or reserving specific memory positions and hid4java causing the error at accessing to those sections or vice-versa.

For some obscure reason, it stopped to fail when we modified certain routines of the project' startup. Basically, we moved some Spring configuration code to a different stage of the bootstrapping and after the change, the issue was gone.

It's really rare but, to be honest, we have not delved into the problem. I just think you were right on your assumptions.

That said, it makes me think about something I told early about the possibility to make both Java Libs to work together within the same project. Maybe it's not a good idea.

gary-rowe commented 3 years ago

Thanks @Laivindur. Given that this was resolved in your project (albeit with mysterious Spring magic) I'll mark it as closed. I've added more info to the wiki in the troubleshooting section to assist others in their application design.

See https://github.com/gary-rowe/hid4java/wiki/Troubleshooting#i-get-a-sigsegv-0xb-when-starting-up for details.