tgoop.com/gdb_dbg/60
Create:
Last Update:
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