سؤال

I am using a Motorola FX9500 RFID reader, which runs Linux with the jamvm version 1.5.0 on it (I can only deploy applications to it - I cannot change the Java VM or anything so my options are limited) - here's what I see when I check the version:

[cliuser@FX9500D96335 ~]$ /usr/bin/jamvm -version
java version "1.5.0"
JamVM version 1.5.4
Copyright (C) 2003-2010 Robert Lougher <rob@jamvm.org.uk>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2,
or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

Build information:

Execution Engine: inline-threaded interpreter with stack-caching
Compiled with: gcc 4.2.2

Boot Library Path: /usr/lib/classpath
Boot Class Path: /usr/local/jamvm/share/jamvm/classes.zip:/usr/share/classpath/glibj.zip

I need to write an application so I grabbed the Oracle Java SDK 1.5.0 and installed it onto my Windows 7 PC, so it has this version:

C:\>javac -version
javac 1.5.0

Am I being too idealistic in considering that an application I compile with that compiler would work correctly on the aforementioned JamVM? Anyway, pressing on in ignorance I write this little application:

public final class TestApp {
    public static void main(final String[] args) {
        long p = Long.MIN_VALUE;
        int o = (int)(-(p + 10) % 10);
        System.out.println(o);
    }
}

Compile it with the aforementioned javac compiler and run it on the PC like so:

C:\>javac TestApp.java

C:\>java TestApp
8

All fine there. Life is good, so I take that .class file and place it on the FX9500 and run it like so:

[cliuser@FX9500D96335 ~]$ /usr/bin/jamvm TestApp
-2

Eek, what the...as you can see - it returns a different result.

So, why and who's wrong or is this something like the specification is not clear about how to deal with this calculation (surely not)? Could it be that I need to compile it with a different compiler?


Why Do I Care About This?

The reason I came to this situation is that a calculation exactly like that happens inside java.lang.Long.toString and I have a bug in my real application where I am logging out a long and getting a java.lang.ArrayIndexOutOfBoundsException. Because the value I am wanting to log may very well be at the ends of a Long.

I think I can work around it by checking for Long.MIN_VALUE and Long.MAX_VALUE and logging "Err, I can't tell you the number but it is really Long.XXX, believe me, would I lie to you?". But when I find this, I feel like my application is built on a sandy foundation now and it needs to be really robust. I am seriously considering just saying that JamVM is not up to the job and writing the application in Python (since the reader also has a Python runtime).

I'm kind of hoping that someone tells me I'm a dullard and I should have compiled it on my Windows PC like .... and then it would work, so please tell me that (if it is true, of course)!


Update

Noofiz got me thinking (thanks) and I knocked up this additional test application:

public final class TestApp2 {
    public static void main(final String[] args) {

        long p = Long.MIN_VALUE + 10;

        if (p != -9223372036854775798L) {
            System.out.println("O....M.....G");
            return;
        }

        p = -p;

        if (p != 9223372036854775798L) {
            System.out.println("W....T.....F");
            return;            
        }

        int o = (int)(p % 10);

        if (o != 8) {
            System.out.println("EEEEEK");
            return;
        }

        System.out.println("Phew, that was a close one");
    }
}

I, again, compile on the Windows machine and run it.

It prints Phew, that was a close one

I copy the .class file to the contraption in question and run it.

It prints...

...wait for it...

W....T.....F

Oh dear. I feel a bit woozy, I think I need a cup of tea...

Update 2

One other thing I tried, that did not make any difference, was to copy the classes.zip and glibj.zip files off of the FX9500 to the PC and then do a cross compile like so (that must mean the compiled file should be fine right?):

javac -source 1.4 -target 1.4 -bootclasspath classes.zip;glibj.zip -extdirs "" TestApp2.java

But the resulting .class file, when run on the reader prints the same message.

هل كانت مفيدة؟

المحلول

I wrote JamVM. As you would probably guess, such errors would have been noticed by now, and JamVM wouldn't pass even the simplest of test suites with them (GNU Classpath has its own called Mauve, and OpenJDK has jtreg). I regularly run on ARM (the FX9500 uses a PXA270 ARM) and x86-64, but various platforms get tested as part of IcedTea.

So I haven't much of a clue as to what's happened here. I would guess it only affects Java longs as these are used infrequently and so most programs work. JamVM maps Java longs to C long longs, so my guess would be that the compiler used to build JamVM is producing incorrect code for long long handling on the 32-bit ARM.

Unfortunately there's not much you can do (apart from avoid longs) if you can't replace the JVM. The only thing you can do is try and turn the JIT off (a simple code-copying JIT, aka inline-threading). To do this use -Xnoinlining on the command line, e.g.:

jamvm -Xnoinlining ...

نصائح أخرى

The problem is in different modulus implementations:

public static long mod(long a, long b){
    long result = a % b;
    if (result < 0)
    {
        result += b;
    }
    return result;
}

this code returns -2, while this:

public static long mod2(long a, long b){
    long result = a % b;
    if (result > 0 && a < 0)
    {
        result -= b;
    }
    return result;
}

returns 8. Reasons why JamVM is doing this way are behind my understanding.

From JLS:

15.17.3. Remainder Operator %

The remainder operation for operands that are integers after binary numeric promotion (§5.6.2) produces a result value such that (a/b)*b+(a%b) is equal to a.

According to this JamVM breaks language specification. Very bad.

I would have commented, but for some reason, that requires reputation.

Long negation doesn't work on this device. I don't understand its exact nature, but if you do two unary minuses you do get back to where you started, e.g. x=10; -x==4294967286; -x==10. 4294967286 is very close to Integer.MAX_VALUE*2 (2147483647*2 = 4294967294). It's even closer to Integer.MAX_VALUE*2-10!

It seems to be isolated to this one operation, and doesn't affect longs in a further fundamental way. It's simple to avoid the operation in your own code, and with some dextrous abuse of the bootclasspath can avoid the calls in GNU Classpath code, replacing them with *-1s. If you need to start your application from the device GUI, you can include the -Xbootclasspath=... switch into the args parameter for it to be passed to JamVM).

The bug is actually already fixed in latter (than the latest release) JamVM code: * https://github.com/ansoncat/jamvm/commit/736c2cb76baf1fedddc1eda5825908f5a0511373 * https://github.com/ansoncat/jamvm/commit/ac83bdc886ac4f6e60d684de1b4d0a5e90f1c489

though doesn't help us with the fixed version on the device. Rob Lougher has mentioned this issue as a reason for releasing a new version of JamVM, though I don't know when this would be, or whether Motorola would be enough convinced to update their firmware.

The FX9500 is actually a repackaged Sirit IN610, meaning that both devices share this bug. Sirit are way friendlier that Motorola and are providing a firmware upgrade, to be available in the near future. I hope that Motorola will also include the fix, though I don't know the details of the arrangement between the two parties.

Either way, we have a very big application running on the FX9500, and the long negation operation hasn't proved to be an impassable barrier.

Good luck, Dan.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top