Native library working incorrectly on some JVMs

Viewed 165

I'm trying to use a native library in java using JNA. The library loads, and I can call methods, but on some JVM distributions the native library does not behave as expected. Some results:

JDK distributions              Behaves as expected
Oracle JDK 1.8.0_191           yes
Liberica JDK 1.8u192           no
Liberica JDK 1.8u275           no
AdoptOpenJDK OpenJ9 1.8u272    yes
AdoptOpenJDK OpenJ9 15.0.1     no
AdoptOpenJDK Hotspot 1.8u275   no
AdoptOpenJDK Hotspot 14.0.1    no

Here expected bahaviour is getting getting hardware info via LC_get_hardware_info and then exiting without errors.

No JDK9+ distributions seems to work, some JDK8 distributions work, some don't (edit) all tested JVMs are 64-bit.

The code (edit: not calling LC_get_hardware_info does not change behaviour, just there's no output in case of success), values returned by SenseLC are error codes:

public static void main(String[] args) {        
    System.setProperty("jna.library.path", System.getProperty("user.dir"));

    SenseLC lib = SenseLC.INSTANCE;

    PointerByReference handle = new PointerByReference(new Memory(8));
    
    int ret = lib.LC_open(0, 0, handle);
    if (ret != SenseLC.LC_SUCCESS) {
        System.out.println("Open: " + SenseLCUtil.getCodeName(ret));
        return;
    }
    System.out.println("Handle: " + handle.getValue());

    LC_hardware_info hi = new LC_hardware_info();
    ret = lib.LC_get_hardware_info(handle.getValue(), hi);
    if (ret != SenseLC.LC_SUCCESS) {
        System.out.println("Read: " + SenseLCUtil.getCodeName(ret));
        return;
    }

    System.out.println(hi);

    ret = lib.LC_close(handle.getValue());
    if (ret != SenseLC.LC_SUCCESS) {
        System.out.println("Close: " + SenseLCUtil.getCodeName(ret));
        return;
    }

}

SenseLCUtil.getCodeName just maps return codes to strings

Expected bahavior:

All calls to SenseLC return 0 (LC_SUCCESS), after invoking ret = lib.LC_get_hardware_info(handle.getValue(), hi);, System.out.println(hi); prints:

LC_hardware_info{
developerNumber={somenumber}
serialNumber={somenumber}
setDate={somenumber}
reservation=0
}

Failure behaviour:

Invoking int ret = lib.LC_open(0, 0, handle); is successfull (ret=0 after call), then after running ret = lib.LC_get_hardware_info(handle.getValue(), hi); ret=3 (LC_INVALID_PARAMETER), and the program output is:

Read: LC_INVALID_PARAMETER

It seems as if an incorrect handle is returned from native code on some JDKs.

Edit 1: added LC_hardware_info and SenseLC classes

LC_hardware_info:

@FieldOrder({
    "developerNumber",
    "serialNumber",
    "setDate",
    "reservation"
})
public class LC_hardware_info extends Structure {

    public int developerNumber;
    public byte[] serialNumber = new byte[8];
    public int setDate;
    public Pointer reservation;

    public String getSerial() {
        return new String(serialNumber, StandardCharsets.UTF_8);
    }

    @Override
    public String toString() {
        return "LC_hardware_info{"
            + "\ndeveloperNumber=" + developerNumber
            + "\nserialNumber=" + getSerial()
            + "\nsetDate=" + setDate
            + "\nreservation=" + reservation
            + "\n}";
    }

}

SenseLC:

public interface SenseLC extends Library {
 
    int LC_SUCCESS = 0; // Successful
    int LC_OPEN_DEVICE_FAILED = 1; // Open device failed
    int LC_FIND_DEVICE_FAILED = 2; // No matching device was found
 
    int LC_INVALID_PARAMETER = 3; // Parameter Error
    int LC_INVALID_BLOCK_NUMBER = 4; // Block Error
    int LC_HARDWARE_COMMUNICATE_ERROR = 5; // Communication error with hardware
    int LC_INVALID_PASSWORD = 6; // Invalid Password
    int LC_ACCESS_DENIED = 7; // No privileges
    int LC_ALREADY_OPENED = 8; // Device is open
    int LC_ALLOCATE_MEMORY_FAILED = 9; // Allocate memory failed
    int LC_INVALID_UPDATE_PACKAGE = 10; // Invalid update package
    int LC_SYN_ERROR = 11; // thread Synchronization error
    int LC_OTHER_ERROR = 12; // Other unknown exceptions
 
    SenseLC INSTANCE = (SenseLC) Native.load("Sense_LC", SenseLC.class);
 
    int LC_open(int vendor, int index, PointerByReference handle);
    int LC_close(Pointer handle);
    int LC_passwd(Pointer handle, int type, byte[] passwd);
    int LC_read(Pointer handle, int block, byte[] buffer);
    int LC_write(Pointer handle, int block, byte[] buffer);
    int LC_encrypt(Pointer handle, byte[] plaintext, byte[] ciphertext);
    int LC_decrypt(Pointer handle, byte[] ciphertext, byte[] plaintext);
    int LC_set_passwd(Pointer handle, int type, byte[] passwd, int retries);
    int LC_change_passwd(Pointer handle, int type, byte[] oldpasswd, byte[] newpasswd);
    int LC_get_hardware_info(Pointer handle, LC_hardware_info info);
    int LC_get_software_info(LC_software_info info);
    int LC_Hmac(Pointer handle, byte[] text, int textlen, byte[] digest);
    int LC_Hmac_software(byte[] text, int textlen, byte[] key, byte[] digest);
    int LC_update(Pointer handle, byte[] buffer);
    int LC_set_key(Pointer handle, int type, byte[] key);
    int LC_gen_update_pkg(byte[] serial, int block, byte[] buffer, byte[] key, byte[] uptPkg);
 
}

Edit 2 library header : lc.h, part of the sdk (Clave2)

Edit 3: Updated JNA mappings, problem persist. Old code can be found here

1 Answers

The fact that you are getting differing results on different JVMs is indicative of undefined behavior. In the cases where it's appearing to work, you're just getting lucky.

As has been discussed in the comments, your problem is likely the mapping of the lc_handle_t. The header file indicates it's a pointer size on 64-bit machines, and int size on 32-bit machines. So you should use PointerByReference when you see * lc_handle_t (when opening to get the handle) and then use getValue() on that returned handle for the other uses of the lc_handle_t.

That type also appears in the reservation field of your LC_hardware_info structure, which should be mapped to Pointer.

Related