Notice: file_put_contents(): Write of 15479 bytes failed with errno=28 No space left on device in /var/www/tgoop/post.php on line 50

Warning: file_put_contents(): Only 8192 of 23671 bytes written, possibly out of free disk space in /var/www/tgoop/post.php on line 50
Алло, это отладочная?@gdb_dbg P.60
GDB_DBG Telegram 60
Близкие сигналы второй степени (часть 1)

Обещал
рассказать ответ на паззлер от Андрея Паньгина, который на выходных разгадывал.

Вообще, как справедливо заметили в комментах, Андрей уже подробно описал суть паззлера и ответ на него. Но давайте расскажу и я, с пояснениями, как именно я пришел к тому же ответу, а еще чуть уточню детали по ходу дела. На русском опять таки, может это кому-то важно.



Итак, суть паззлера. Имеем код:

public class Test {
static volatile Test T = new Test();
int n;

public static void main(String[] args) {
while (true) {
Test a = T, b = T, c = T, d = T;
a.n = b.n = c.n = d.n;
}
}
}


Как заставить такой код выбросить NullPointerException без изменения кода? А еще без запуска его в нативном отладчике, хаков с памятью и использования инструментарии.

Ложные следы

Первая мысль, которая мне пришла в голову - это сломать clinit класса, но при этом как-то дотянуть исполнение до main метода, думал про верификацию и т.д., но это все мимо. Похожие ответы есть и в реплаях к тесту, где предлагается заиспользовать кастомный класслоадер, который просто сразу будет бросать NPE, но все это совершенно не связано с кодом в main, так что здесь явно что-то другое.

А вот посмотрев на тело метода main можно догадаться, что автор паззлера хочет, чтобы постоянно происходили разыменования полей. А это ведь то самое место, где и правда (с точки зрения JVM) может случиться NPE.

Неявные исключения

Теперь вспоминаем про оптимизацию налчеков. С учетом того, что в строчке a.n = b.n = c.n = d.n NPE может полететь при каждом обращению к n, самым наивным решением будет сгенерировать код, при каждом доступе проверяет на null и в случае чего бросает NPE. Что-то типа:

if (c == null || d == null) {
throw new NullPointerException();
}
c.n = d.n;
if (b == null || c == null) {
throw new NullPointerException();
}
b.n = c.n;
...
if (a == null || b == null) {
throw new NullPointerException();
}
a.n = b.n;


или может что-то чуть более оптимизированное. Но вы представляете, насколько же это будет дорого? Если КАЖДЫЙ доступ к полю будет вызывать вот такое ветвление, все бы вообще едва ползало.

Поэтому тут вход вступает моя самая любимая оптимизация: implicit null checks. Алексей Шипилев прекрасно описал ее здесь, а я уже рассказывал про нее вот тут.

После работы оптимизирующего компилятора вместо лапши из условий мы получим для строчки a.n = b.n = c.n = d.n простой и понятный asm:

mov esi, dword [rsi+10H]
mov dword [rdx+10H], esi
mov dword [rcx+10H], esi
mov dword [rax+10H], esi


Как же это работает? Максимально прямолинейно: если, скажем, в rsi у вас лежит 0 (случай, когда должно полететь NPE), мы и правда попробуем разыменовать десятку: mov esi, dword [10h]. Что, конечно, приведет к развалу. А точнее к поднятию операционной системой сигнала SIGSEGV.

Но с сигналами вот какая штука: их можно перехватывать. Вызываете сискол sigaction, указываете обработчик, который должен сработать, если процесс получил соответствующий сигнал. В этом обработчике вы также получите много интересной информации: какая инструкция вызвала поднятие сигнала, по какому адресу был доступ, контекст (значения регистров) треда, где это произошло, наконец.

Предприимчивые рантаймщики этим активно пользуются для оптимизации NPE. Случился вот у вас SIGSEGV, рантайм аккуратно посмотрит на контекст, поймет, что развал произошел в Java треде; что адрес доступа около нуля; что текущая инструкция соответствует доступу к полю. И на основе всей этой информации вместо того, чтобы крэшнуть JVM, выбросит NPE. Вот место в коде хотспота, где происходит соответствующая обработка.

Оцените красоту этой оптимизации! В хорошем (и самом частом) случае доступ к полям теперь абсолютно бесплатен. В плохих случаях, когда летит NPE, вы, конечно, заплатите огромную цену (поднятие сигнала операционной системой - дело дорогое). Но так ведь они и редко случаются. А на случай если вдруг начинают случаться часто, Хотспот умеет деоптимизировать код и вставлять явную проверку. Красиииво. ↓

#дух_машины



tgoop.com/gdb_dbg/60
Create:
Last Update:

Близкие сигналы второй степени (часть 1)

Обещал
рассказать ответ на паззлер от Андрея Паньгина, который на выходных разгадывал.

Вообще, как справедливо заметили в комментах, Андрей уже подробно описал суть паззлера и ответ на него. Но давайте расскажу и я, с пояснениями, как именно я пришел к тому же ответу, а еще чуть уточню детали по ходу дела. На русском опять таки, может это кому-то важно.



Итак, суть паззлера. Имеем код:

public class Test {
static volatile Test T = new Test();
int n;

public static void main(String[] args) {
while (true) {
Test a = T, b = T, c = T, d = T;
a.n = b.n = c.n = d.n;
}
}
}


Как заставить такой код выбросить NullPointerException без изменения кода? А еще без запуска его в нативном отладчике, хаков с памятью и использования инструментарии.

Ложные следы

Первая мысль, которая мне пришла в голову - это сломать clinit класса, но при этом как-то дотянуть исполнение до main метода, думал про верификацию и т.д., но это все мимо. Похожие ответы есть и в реплаях к тесту, где предлагается заиспользовать кастомный класслоадер, который просто сразу будет бросать NPE, но все это совершенно не связано с кодом в main, так что здесь явно что-то другое.

А вот посмотрев на тело метода main можно догадаться, что автор паззлера хочет, чтобы постоянно происходили разыменования полей. А это ведь то самое место, где и правда (с точки зрения JVM) может случиться NPE.

Неявные исключения

Теперь вспоминаем про оптимизацию налчеков. С учетом того, что в строчке a.n = b.n = c.n = d.n NPE может полететь при каждом обращению к n, самым наивным решением будет сгенерировать код, при каждом доступе проверяет на null и в случае чего бросает NPE. Что-то типа:

if (c == null || d == null) {
throw new NullPointerException();
}
c.n = d.n;
if (b == null || c == null) {
throw new NullPointerException();
}
b.n = c.n;
...
if (a == null || b == null) {
throw new NullPointerException();
}
a.n = b.n;


или может что-то чуть более оптимизированное. Но вы представляете, насколько же это будет дорого? Если КАЖДЫЙ доступ к полю будет вызывать вот такое ветвление, все бы вообще едва ползало.

Поэтому тут вход вступает моя самая любимая оптимизация: implicit null checks. Алексей Шипилев прекрасно описал ее здесь, а я уже рассказывал про нее вот тут.

После работы оптимизирующего компилятора вместо лапши из условий мы получим для строчки a.n = b.n = c.n = d.n простой и понятный asm:

mov esi, dword [rsi+10H]
mov dword [rdx+10H], esi
mov dword [rcx+10H], esi
mov dword [rax+10H], esi


Как же это работает? Максимально прямолинейно: если, скажем, в rsi у вас лежит 0 (случай, когда должно полететь NPE), мы и правда попробуем разыменовать десятку: mov esi, dword [10h]. Что, конечно, приведет к развалу. А точнее к поднятию операционной системой сигнала SIGSEGV.

Но с сигналами вот какая штука: их можно перехватывать. Вызываете сискол sigaction, указываете обработчик, который должен сработать, если процесс получил соответствующий сигнал. В этом обработчике вы также получите много интересной информации: какая инструкция вызвала поднятие сигнала, по какому адресу был доступ, контекст (значения регистров) треда, где это произошло, наконец.

Предприимчивые рантаймщики этим активно пользуются для оптимизации NPE. Случился вот у вас SIGSEGV, рантайм аккуратно посмотрит на контекст, поймет, что развал произошел в Java треде; что адрес доступа около нуля; что текущая инструкция соответствует доступу к полю. И на основе всей этой информации вместо того, чтобы крэшнуть JVM, выбросит NPE. Вот место в коде хотспота, где происходит соответствующая обработка.

Оцените красоту этой оптимизации! В хорошем (и самом частом) случае доступ к полям теперь абсолютно бесплатен. В плохих случаях, когда летит NPE, вы, конечно, заплатите огромную цену (поднятие сигнала операционной системой - дело дорогое). Но так ведь они и редко случаются. А на случай если вдруг начинают случаться часто, Хотспот умеет деоптимизировать код и вставлять явную проверку. Красиииво. ↓

#дух_машины

BY Алло, это отладочная?




Share with your friend now:
tgoop.com/gdb_dbg/60

View MORE
Open in Telegram


Telegram News

Date: |

Activate up to 20 bots A Telegram channel is used for various purposes, from sharing helpful content to implementing a business strategy. In addition, you can use your channel to build and improve your company image, boost your sales, make profits, enhance customer loyalty, and more. 1What is Telegram Channels? In 2018, Telegram’s audience reached 200 million people, with 500,000 new users joining the messenger every day. It was launched for iOS on 14 August 2013 and Android on 20 October 2013. Content is editable within two days of publishing
from us


Telegram Алло, это отладочная?
FROM American