java-native-access / java-native-access/jna

Incorrect maximum alignment for linux-x86_64

Open
#860 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
8.9k
Forks
1.7k
PR merge metrics
No merged PRs in 30d

Description

JNA does not support the long double. While it is rarely used or necessary by itself, it has importance in constructs such as max_align_t, which may be used in APIs and structures that attempt to encapsulate generic data.

I specifically tested everything on Ubuntu 17.04 with default packages. This is my environment:

kfyatek@kfyatek:/tmp$ uname -a
Linux kfyatek 4.10.0-33-generic #37-Ubuntu SMP Fri Aug 11 10:55:28 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
kfyatek@kfyatek:/tmp$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/6/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 6.3.0-12ubuntu2' --with-bugurl=file:///usr/share/doc/gcc-6/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-6 --program-prefix=x86_64-linux-gnu- --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 --enable-default-pie --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-6-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-6-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-6-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 6.3.0 20170406 (Ubuntu 6.3.0-12ubuntu2) 
kfyatek@kfyatek:/tmp$ java -version
openjdk version "1.8.0_131"
OpenJDK Runtime Environment (build 1.8.0_131-8u131-b11-2ubuntu1.17.04.3-b11)
OpenJDK 64-Bit Server VM (build 25.131-b11, mixed mode)

On my system, the ABI defines the maximum alignment as 16 bytes:

kfyatek@kfyatek:/tmp$ cat test.c
#include <stddef.h>
#include <stdio.h>

typedef struct {
	char ch;
	max_align_t align;
} test_t;

int main() {
	printf("%d\n", (int) _Alignof(max_align_t));
	printf("%d\n", (int) offsetof(test_t, align));
}
kfyatek@kfyatek:/tmp$ gcc test.c -o test
kfyatek@kfyatek:/tmp$ ./test
16
16

The same result can be achieved by writing something like a "poor man's max_align_t" manually:

kfyatek@kfyatek:/tmp$ cat test2.c
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

typedef union {
	void *data_pointer;
	void (*code_pointer)();
	long double largest_float;
	intmax_t largest_integer;
} poor_mans_max_align_t;

typedef struct {
	char ch;
	poor_mans_max_align_t align;
} test_t;

int main() {
	printf("%d\n", (int) _Alignof(poor_mans_max_align_t));
	printf("%d\n", (int) offsetof(test_t, align));
}
kfyatek@kfyatek:/tmp$ gcc test2.c -o test2
kfyatek@kfyatek:/tmp$ ./test2
16
16

Or even just long double alone:

kfyatek@kfyatek:/tmp$ cat test3.c
#include <stddef.h>
#include <stdio.h>

typedef struct {
	char ch;
	long double ld;
} test_t;

int main() {
	printf("%d\n", (int) _Alignof(long double));
	printf("%d\n", (int) offsetof(test_t, ld));
}
kfyatek@kfyatek:/tmp$ gcc test3.c -o test3
kfyatek@kfyatek:/tmp$ ./test3
16
16

Now, while I cannot meaningfully access long double via JNA, I can encapsulate it in a structure with appropriate size and alignment. But when I try to use it in another structure, things doesn't go so smoothly:

kfyatek@kfyatek:/tmp$ cat Test.java
import com.sun.jna.*;
import java.util.*;

public class Test {
    public static class NativeLongDouble extends Structure {
        public byte[] data = new byte[16];

        public NativeLongDouble() {
            super();
        }

        public NativeLongDouble(Pointer peer) {
            super(peer);
        }

        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList("data");
        }

        @Override
        protected int getNativeAlignment(Class<?> type, Object value, boolean isFirstElement) {
            return 16;
        }
    }

    public static class TestStruct extends Structure {
        public byte ch;
        public NativeLongDouble ld;

        public TestStruct() {
            super();
        }

        public TestStruct(Pointer peer) {
            super(peer);
        }

        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList("ch", "ld");
        }
    }

    public static void main(String[] args) {
        System.out.println(new TestStruct());
    }
}
kfyatek@kfyatek:/tmp$ javac -cp jna-4.4.0.jar Test.java
kfyatek@kfyatek:/tmp$ java -cp jna-4.4.0.jar:. Test
Test$TestStruct(auto-allocated@0x7f02bc213f70 (24 bytes)) {
  byte ch@0=0
  Test$NativeLongDouble ld@8=Test$NativeLongDouble(native@0x0) (16 bytes) {
    byte data[16]@0=[B@7ef20235
  }
}

Note: for more accurate code, I should query some native library; I actually attempted accessing com.sun.jna.Structure$FFIType$FFITypes via reflection in my more production-ish attempt to solve this problem; I hardcoded 16 here for brevity.


As you can see, we cannot reliably reproduce layout of any struct that includes a long double (or anything derived from it, such as max_align_t) with JNA on Linux x86_64.

This seems to stem from two reasons:

  • Native.MAX_ALIGNMENT is hardcoded to 8 on most platforms, including all Linuxes. On other platforms it's defined to LONG_SIZE, which on Linux x86_64 is 8 anyway; even though real maximum alignment for Linux x86_64 is 16, as demonstrated above.
  • Structure::getNativeAlignment() limits alignment to Native.MAX_ALIGNMENT via Math.min(), so even if the structure's alignment requirements are properly calculated as 16, it gets clamped to 8 due to that, unless getNativeAlignment() is overloaded for all outer structures as well.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Native.MAX_ALIGNMENT and Structure.getNativeAlignment(), the two locations identified in the report, and compare their behavior for Linux x86_64 with the provided C alignment examples. Reproduce the nested TestStruct layout using the supplied Java example; done means structures containing a 16-byte-aligned value match the native offsets instead of being clamped to 8 bytes.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
devtools, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.