PrintAssembly – вывод дизассемблированных java-методов

Written by elwood

Навеяно постом http://elizarov.livejournal.com/29052.html – там описывается, как включить опцию дизассемблирования кода скомпилированных методов. Для этого нужно а) найти плагин hsdis и засунуть его в bin-директорию jdk б) запустить приложение со специальными опциями. От себя ещё добавлю пункт в) добиться того, чтобы метод, код которого хочется получить, был скомпилирован JIT-компилятором, а не просто интерпретирован.

Разбираемся с плагином

В посте лежит ссылка на DLL с hsdis плагином, но только для 32-битных систем. У меня 64-битная Windows 8, поэтому пришлось собирать самому. Делал по инструкции с сайта http://dropzone.nfshost.com/hsdis.htm, всё получилось (правда в первые разы почему-то сборка не прошла до конца по причине отсутствия команды gcc, но я доставил в cygwin пакет с gcc-core и всё собралось). Готовые файлы прилагаю: hsdis-amd64.dll и hsdis-i386.dll

Разбираемся с опциями

Вот такую портянку я использовал

-XX:CompileThreshold=1 -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Program.testMethod -XX:PrintAssemblyOptions=intel -XX:+PrintCompilation -XX:+LogCompilation

-XX:CompileThreshold=1 - чтобы JIT-компилятор компилировал методы после одного вызова
-XX:+UnlockDiagnosticVMOptions - нужная опция, чтобы остальные -XX-опции распознавались
-XX:CompileCommand=print,*Program.testMethod - указываем, что нам нужен дизассемблированный листинг метода testMethod класса Program. Зачем тут звездочка, я не понял, может быть потом разберусь
-XX:PrintAssemblyOptions=intel - устанавливает синтаксис, в котором будет выведен дизассемблированный код. Интеловский синтаксис более удобочитаем, поскольку вокруг регистров отсутствуют проценты %eax%
-XX:+PrintCompilation - выводить список методов, которые скомпилированы в нативный код - для отладки того, что наш искомый метод будет скомпилирован
-XX:+LogCompilation - вывести лог компиляции в файл hotspot.log (по умолчанию)

Разбираемся с компиляцией методов

Наблюдая за тем, как работает тестовая программа, я вывел следующие эмпирические заключения. За их корректность не ручаюсь, но судя по экспериментам, дело обстоит именно так. Методы компилируются после выхода из них и компилируются асинхронно, то есть кладутся в некую очередь для JIT-компилятора, и он потом их компилирует. Таким образом, если вы хотите посмотреть листинг метода main(), то навряд ли это у вас получится, потому что сразу же после выхода из main() программа завершится, и JIT-компилятор не успеет её скомпилировать. Я ставил задержки Thread.sleep(1000) после вызова нужного метода, и рантайм успевал скомпилировать его и вывести листинг в лог. Если же вас интересует именно main, то придётся вынести его тело в другой метод, и вызвать его из основного main, добавив после вызова задержку по времени.

Установка midnight commander на Android

Written by elwood

Предварительно необходимо

Рутованный телефон с установленным BusyBox и один из SSH серверов (я использую SSH/SFTP Daemon).

Алгоритм

Скачиваем архив с собранным под ARM бинарником, запускаем SSH сервер на телефоне, подключаемся к нему через Putty, разблокируем файловую систему /system для записи:

su -
busybox mount -o remount,rw /system

Подключаемся через WinSCP, раскидываем файлы так, как они размещены в архиве.
Добавляем +x на файлы /data/xbin/* :

chmod +x /data/xbin/*

Также устанавливаем права тут (хотя вроде и без этого работает):

busybox chmod -R 0755 /system/etc/terminfo
busybox chmod -R 0644 /system/etc/terminfo/?/*

После этого проверяем, что /data/xbin/mc стартует в сеансе Putty, и возвращаем read-only на /system:

busybox mount -o remount,ro /system

Результат

Вполне рабочий mc с работающим вьювером (редактор глючит, видимо надо что-то пофиксить в конфигах или добавить). Остальные плагины пока не смотрел.

Запуск и остановка фоновых потоков в Java

Written by elwood

Потокобезопасный вариант остановки потока по требованию. Паттерн, который мы повсеместно применяли в .net. В java-мире аналогичное поведение можно более просто реализовать с помощью механизма interruptions, но этот способ более гибок, поскольку во-первых не зависит от тонкостей обработки InterruptException (а это довольно хитрая штука), а во-вторых, более расширяем – например, по сигналу в condition object можно не только завершать выполнение потока, но и передавать какие-то события внутрь потока – например, некоторое действие нужно выполнять либо по таймеру, либо по сигналу. И мы внутри функции потока всегда будем делать нужные операции сразу же по мере необходимости. В отличие от традиционного механизма wait-notify (которое не дает инфы о причине завершения ожидания – таймауте или внешнем сигнале), в этом API мы всегда увидим, когда прошел таймаут. Мы не всегда сможем определить, был ли вызван signal() из-за одного пограничного случая (если и эта информация нужна, то можно её получить просто добавив флаг), но факт прохождения таймаута мы будем знать достоверно.

    private ReentrantLock reentrantLock = new ReentrantLock(  );
    private Condition condition= reentrantLock.newCondition();
    private volatile boolean running = false;
    private static final long PERIOD_MILLISECONDS = 5000;
 
    private Thread calculationThread = new Thread( new Runnable() {
        public void run() {
            log.info( "Calculation thread started." );
            for (;;) {
 
                // todo : здесь собственно идёт полезная работа потока
 
                reentrantLock.lock();
                try {
                    boolean awaitResult = false;
 
                    // в этом месте мы проверяем running для ситуации, когда stopThread()
                    // был вызван в момент, когда этот поток не находился в ожидании, и вызов
                    // signal() не привел ни к чему
                    if ( !running )
                        break;
                    try {
                        // При вызове await() занятая нами блокировка будет освобождена
                        // Но при возврате управления метод await() снова должен будет взять блокировку
                        // Интересный механизм, позволяющий нам точно знать, в какой последовательности
                        // будут выполнены инструкции после вызова signal() и собственно пробуждение потока
                        awaitResult = condition.await(PERIOD_MILLISECONDS, TimeUnit.MILLISECONDS );
                    } catch ( InterruptedException e ) {
                        log.error( "Calculation thread interrupted", e );
                        break;
                    }
                    // running проверять обязательно, поскольку awaitResult может быть false
                    // даже в случае вызова signal (если к этому времени подошел таймаут) - проверено
                    // Это и есть тот самый пограничный случай
                    if (awaitResult || !running )
                        break;
                } finally {
                    reentrantLock.unlock();
                }
            }
            log.info( "Calculation thread stopped." );
        }
    } );
 
    private void startCalculationThread() {
        Assert.assertTrue( !running );
        log.info( "Starting calculation thread.." );
        running = true;
        calculationThread.start();
    }
 
    private void stopCalculationThread() {
        Assert.assertTrue( running );
        log.info( "Stopping calculation thread.." );
        reentrantLock.lock();
        try {
            running = false;
            // Посылаем сигнал в наш condition object
            // Если в это время поток ждет на вызове await(), то он возобновит выполнение
            // НО только после того, как await() получит блокировку
            // Таким образом, сначала будет выполнен весь код после вызова signal() - 
            // в нашем случае это запись в лог сообщения, и только потом await() получит блокировку и вернёт управление
            condition.signal();
            log.info("Calculation thread signalled to stop." );
        } finally {
            reentrantLock.unlock();
        }
        // ожидаем завершения потока
        calculationThread.join();
    }