Комментарии 0
...комментариев пока нет
[Перевод] Когда кажется, что нашёл баг в компиляторе
Здесь компилятор (неверно) поставил операцию сохранения раньше операции сдвига. В таком случае вы удивитесь, насколько изменится результат. Пытаясь отлаживать проблемы такого характера, приходится проверять ваши допущения на каждом шаге, вооружившись отладчиком или printf (и надеяться, что ни один из этих инструментов не повлияет на состояние кода). Что, согласно вашим ожиданиям, происходит в каждой строке кода, и соответствует ли ассемблер вашим допущениям? Именно такие вопросы следует себе задавать, занимаясь отладкой подобных проблем.
Поэтому, если вы не можете объяснить, почему компилятор выдал именно такой ассемблерный код, значит ли это, что вы нашли в компиляторе баг? Не обязательно. Тот фрагмент кода, который я привела выше, определённо содержит ошибки, но в ассемблере не всегда удаётся найти точные соответствия коду на высокоуровневых языках. В частности, в языке C есть богатая история неопределённых поведений. Полагаясь на неопределённое или на недостаточно чётко описанное поведение, вы наверняка будете удивляться выдаче компилятора. Чтобы разобраться, нашли ли вы баг или просто неожиданное поведение, нужно обратиться за помощью к специалистам по компилятору, глубоко понимающим язык. Можно отлаживать компилятор самостоятельно, но в современных компиляторах применяются многоходовые оптимизации, и бывает сложно определить, какой шаг оптимизации за что отвечает. Когда собираетесь сообщить о проблеме с компилятором, помните один ключевой момент: команда по поддержке компилятора не знает вашей базы кода, поэтому, если вы можете подобрать максимально узкий тестовый кейс, не охватывающий ничего лишнего кроме самой проблемы, то лучше всего так и сделать. При работе с gcc можно передать компилятору
Такова история последнего бага, вдохновившего мен на этот пост. Я отлаживала одну проблему (на Rust, а не на C!) и заметила, что переменная изменяется после системного вызова, который, казалось бы, вообще не должен был затрагивать переменные. Потребовалось хорошо покопаться в дампе ассемблера, чтобы понять, что для считывания переменной после системного вызова использовался регистр, содержимое которого не сохраняется. В частности, код использовал указатель кадра в качестве регистра общего назначения, и компилятор обрабатывал эту ситуацию неверно. Хорошее напоминание о том, что в компиляторах, как ив любых других программах, часто возникают проблемы с обработкой пограничных случаев. Кроме того, в компиляторах требуется очень дисциплинированно предусматривать тесты на все случаи, чтобы впоследствии не приходилось откатываться. Особенно много проблем возникает с ассемблерными вставками, поскольку при их программировании нужно на самом фундаментальном уровне разбираться в механизме выделения регистров. Опять же, снимаю шляпу перед разработчиками компиляторов, благодаря которым большинству из нас (почти) никогда не приходится беспокоиться о том, что же делает компилятор.
Я написала этот пост на тему багов компилятора, но он также касается и аппаратных багов. Вы ожидаете от аппаратной части определённого поведения и, если она проявляет себя не так, как следует, то отлаживать такие случаи бывает очень сложно. Поэтому определитесь с тем, из каких допущений вы исходите (и даже какие подразумеваете) и выясните, где эти допущения не соблюдаются. Это важнейший навык при отладке.
Поэтому, если вы не можете объяснить, почему компилятор выдал именно такой ассемблерный код, значит ли это, что вы нашли в компиляторе баг? Не обязательно. Тот фрагмент кода, который я привела выше, определённо содержит ошибки, но в ассемблере не всегда удаётся найти точные соответствия коду на высокоуровневых языках. В частности, в языке C есть богатая история неопределённых поведений. Полагаясь на неопределённое или на недостаточно чётко описанное поведение, вы наверняка будете удивляться выдаче компилятора. Чтобы разобраться, нашли ли вы баг или просто неожиданное поведение, нужно обратиться за помощью к специалистам по компилятору, глубоко понимающим язык. Можно отлаживать компилятор самостоятельно, но в современных компиляторах применяются многоходовые оптимизации, и бывает сложно определить, какой шаг оптимизации за что отвечает. Когда собираетесь сообщить о проблеме с компилятором, помните один ключевой момент: команда по поддержке компилятора не знает вашей базы кода, поэтому, если вы можете подобрать максимально узкий тестовый кейс, не охватывающий ничего лишнего кроме самой проблемы, то лучше всего так и сделать. При работе с gcc можно передать компилятору
-E
, чтобы он остановился на этапе предобработки и выдал вам самодостаточное завершении файла в .i
. (При работе с ядром Linux можно выполнить make path/to/kernel/file.i
).Такова история последнего бага, вдохновившего мен на этот пост. Я отлаживала одну проблему (на Rust, а не на C!) и заметила, что переменная изменяется после системного вызова, который, казалось бы, вообще не должен был затрагивать переменные. Потребовалось хорошо покопаться в дампе ассемблера, чтобы понять, что для считывания переменной после системного вызова использовался регистр, содержимое которого не сохраняется. В частности, код использовал указатель кадра в качестве регистра общего назначения, и компилятор обрабатывал эту ситуацию неверно. Хорошее напоминание о том, что в компиляторах, как ив любых других программах, часто возникают проблемы с обработкой пограничных случаев. Кроме того, в компиляторах требуется очень дисциплинированно предусматривать тесты на все случаи, чтобы впоследствии не приходилось откатываться. Особенно много проблем возникает с ассемблерными вставками, поскольку при их программировании нужно на самом фундаментальном уровне разбираться в механизме выделения регистров. Опять же, снимаю шляпу перед разработчиками компиляторов, благодаря которым большинству из нас (почти) никогда не приходится беспокоиться о том, что же делает компилятор.
Я написала этот пост на тему багов компилятора, но он также касается и аппаратных багов. Вы ожидаете от аппаратной части определённого поведения и, если она проявляет себя не так, как следует, то отлаживать такие случаи бывает очень сложно. Поэтому определитесь с тем, из каких допущений вы исходите (и даже какие подразумеваете) и выясните, где эти допущения не соблюдаются. Это важнейший навык при отладке.