Return error time что это

Содержание
  1. Решение «Runtime error» при запуске игр на ПК
  2. Причины появления «Runtime error»
  3. Решение ошибки «Runtime error»
  4. Решение №1 Ликвидация кириллицы
  5. Решение №2 Изменение языка программ, не поддерживающих Юникод
  6. Решение №3 Переустановка Visual Studio C++ и .NET Framework
  7. Решение №4 Удаление недавно установленных программ
  8. Решение №5 Восстановление системных файлов
  9. Исследование одного неопределённого поведения
  10. Введение
  11. Читаем Стандарт
  12. Пример 1 — bool
  13. Linux x86-x64 Clang 10.0.0, -O0
  14. Linux x86-x64 Clang 10.0.0, -O1, -O2
  15. Linux x86-x64 gcc 9.3, -O0
  16. Linux x86-x64 gcc 9.3, -O1, -O2, -O3
  17. macOs X Apple clang version 11.0.0, -O0
  18. macOs X Apple clang version 11.0.0, -O1, -O2
  19. Windows MSVC 2019 16.5.4, усложнённый пример, /Od
  20. Windows MSVC 2019 16.5.4, усложнённый пример, /O1, /O2
  21. Вывод по примеру 1
  22. Пример 1a — управление неопределённым поведением
  23. Linux x86-x64 gcc 9.3, -O0
  24. Windows MSVC 2019 16.5.4, /Od
  25. Windows MSVC 2019 16.5.4, /O1, /O2
  26. macOs X Apple clang version 11.0.0, -O0
  27. Вывод по примеру 1a
  28. Пример 1b — сломанный bool
  29. Windows MSVC 2019 16.5.4, /Od
  30. Windows MSVC 2019 16.5.4, /O1, /O2
  31. Linux x86-x64 gcc 9.3, -O0
  32. Вывод по примеру 1b
  33. Пример 2 — struct
  34. Linux x86-x64 Clang 10.0.0, -O1, -O2
  35. Windows MSVC 2019 16.5.4, /Od /RTCs
  36. Пример 2a
  37. Windows MSVC 2019 16.5.4, /Od /RTCs

Решение «Runtime error» при запуске игр на ПК

«Runtime error» появляется при запуске различных приложений, включая и видеоигр, либо в случайные моменты при работе ОС Windows. Ошибка не эксклюзивна для какой-то одной версии Windows — она возникает на всем, начиная от Windows XP и заканчивая последними сборками «десятки». В сегодняшней статье мы расскажем вам, что вызывает «Runtime error» и как от нее избавиться.

Причины появления «Runtime error»

К сожалению, определить точную причину появления данной ошибки невозможно: пользователю необходимо перебирать доступные решения, пока одно из них не устранит проблему. Тем не менее давайте все же взглянем на список, так сказать, «подозреваемых». Вот что может вызывать появление ошибки «Runtime error»:

  • отсутствующие либо поврежденные системные компоненты;
  • конфликт запускаемого приложения со сторонней программой/службой;
  • поврежденные файлы запускаемого приложения;
  • присутствие кириллицы в расположении исполняемого файла приложения;
  • «кривая» установка библиотек Visual Studio C++ и .NET Framework либо их отсутствие.

Решение ошибки «Runtime error»

Решение №1 Ликвидация кириллицы

Ошибка «Runtime error» может возникать при запуске тех программ и игр, в расположении которых присутствуют кириллические символы. Например, на данную ошибку можно наткнуться, если запускаемое приложение находится по пути C:\Users\[ИМЯ АККАУНТА НА РУССКОМ]\Downloads\[КОРНЕВАЯ ПАПКА ПРИЛОЖЕНИЯ]\. Избавьтесь от русского языка по пути к приложению и попробуйте запустить его еще раз.

Решение №2 Изменение языка программ, не поддерживающих Юникод

Появление ошибки «Runtime error» возможно в том случае, если в параметрах региональных стандартов для приложений, не поддерживающих Юникод, задан русский, а не английский язык.

  • Нажмите WIN+R и выполните значение «CONTROL»;
  • кликните на пункт «Изменение форматов даты, времени и чисел» в разделе «Часы и регион»;
  • перейдите во вкладку «Дополнительно» в появившемся окошке «Регион»;
  • нажмите на кнопку «Изменить язык системы…»;
  • в ниспадающем меню выберите «Английский (США)» и сохраните внесенные изменения;
  • перезагрузите ПК.

Запустите проблемное приложение, чтобы проверить наличие ошибки.

Решение №3 Переустановка Visual Studio C++ и .NET Framework

Некорректно установленные (либо отсутствующие в системе) распространяемые библиотеки Microsoft Visual Studio C++ и .NET Framework могут вызвать появление «Runtime error». Чтобы переустановить эти библиотеки, вам нужно сделать следующее:

  • вызовите перед собой Панель управления, как это было показано выше;
  • кликните на «Удаление программы» в разделе «Программы»;
  • найдите в списке программ все версии Visual Studio C++ и удалите их;
  • перейдите на официальный сайт Майкрософт и загрузите необходимые установщики VS C++;
  • проделайте тоже самое с различными версиями .NET Framework на своем ПК;
  • вернитесь к окошку «Программы и компоненты» и кликните на пункт «Включение или отключение компонентов Windows»;
  • убедитесь, что возле всех версий .NET Framework стоят галочки;
  • закройте все открытые окна и перезагрузите ПК.

Решение №4 Удаление недавно установленных программ

Определенные программы могут входить в конфликты с приложениями на компьютере. Ошибка «Runtime error» начала появляться практически сразу после установки какой-то программы или игры? Удалите ее, перезагрузите ПК и попробуйте запустить нужное вам приложение еще раз. Возможно, на сей раз никакой ошибки не появится. Заняться удалением программ можно в «Программы и компоненты» (показано выше ↑).

Решение №5 Восстановление системных файлов

Поврежденные системные файлы — потенциальная причина за появлением ошибки «Runtime error». Благо, в Windows присутствует специальная утилита, задача которой — это восстановление системных файлов. Чтобы пустить эту утилиту в работу, вам нужно сделать на своем ПК следующее:

  • кликните ПКМ на меню Пуск и выберите пункт «Командная строка (администратор)» (PowerShell тоже подойдет);
  • пропишите в консоли команду «SFC /SCANNOW» и нажмите ENTER;
  • дождитесь окончания сканирования и восстановления системных файлов;
  • перезагрузите компьютер.

Ошибка «Runtime error» практически наверняка исчезнет с вашего ПК, особенно если SFC удалось найти и восстановить поврежденные системные файлы.

Источник

Исследование одного неопределённого поведения

В статье исследуются возможные проявления неопределённого поведения, возникающего в c++ при завершении не-void функции без вызова return с подходящим значением. Статья носит больше научно-развлекательный характер, чем практический.

Кому не нравится весело скакать по граблям — проходим мимо, не задерживаемся.

Введение

Всем известно, что при разработке c++-кода следует не допускать неопределённого поведения.
Однако:

  • неопределённое поведение может казаться не достаточно опасным из-за абстрактности возможных последствий;
  • не всегда понятно, где грань.

Попробуем конкретизировать возможные проявления неопределённого поведения, возникающего в одном довольно простом случае — в не-void функции отсутствует return.

Для этого рассмотрим код, генерируемый наиболее популярными компиляторами в разных режимах оптимизации.

Исследования под Linux будут проводиться с помощью Compiler Explorer. Исследования под Windows и macOs X — на непосредственно доступном мне железе.

Все сборки будут делаться для x86-x64.

Никаких мер для усиления либо подавления предупреждений/ошибок компиляторов предприниматься не будет.

Будет много дизассемблированного кода. Его оформление, к сожалению, разношёрстное, т.к. приходится использовать несколько разных инструментов (хорошо хоть удалось добиться везде синтаксиса Intel). К дизассемблированному коду я буду давать в меру подробные комментарии, которые, однако, не избавляют от необходимости знания регистров процессора и принципов работы стека.

Читайте также:  Window error recovery lenovo

Читаем Стандарт

6.6.3 The return statement

Flowing off the end of a function is equivalent to a return with no value; this results in undefined
behavior in a value-returning function.

Достижение конца функции эквивалентно выражению return без возвращаемого значения; для функции, у которой возвращаемое значение предусмотрено, это приводит к неопределённому поведению.

9.6.3 The return statement

Flowing off the end of a constructor, a destructor, or a function with a cv void return type is equivalent to a return with no operand. Otherwise, flowing off the end of a function other than main (6.8.3.1) results in undefined behavior.

Достижение конца конструктора, деструктора или функции с возвращаемым значением void (возможно, с квалификаторами const и volatile) эквивалентно выражению return без возвращаемого значения. Для всех других функций это приводит к неопределённому поведению (кроме функции main).

Что это значит на практике?

Если сигнатура функции предусматривает возвращаемое значение:

  • её выполнение должно завершаться выражением return с экземпляром подходящего типа;
  • иначе — неопределённое поведение;
  • неопределённое поведение начинается не с момента вызова такой функции и не с момента использования возвращённого значение, а с момента ненадлежащего завершения такой функции;
  • если функция содержит как корректные, так и некорректные пути выполнения — неопределённое поведение будет возникать только на некорректных путях;
  • рассматриваемое неопределённое поведение не затрагивает выполнение инструкций, содержащихся в теле функции.

Фраза про функцию main не является новшеством c++17 — в предыдущих версиях Стандарта аналогичное исключение было описано в разделе 3.6.1 Main function.

Пример 1 — bool

В c++ нет ни одного типа с состоянием более простым, чем bool. Вот с него и начнём.

MSVC выдаёт на такой пример ошибку компиляции C4716, поэтому для MSVC код придётся слегка усложнить, предоставив хотя бы один корректный путь выполнения:

Платформа Компилятор Результат компиляции
Linux x86-x64 Clang 10.0.0 warning: non-void function does not return a value [-Wreturn-type]
Linux x86-x64 gcc 9.3 warning: no return statement in function returning non-void [-Wreturn-type]
macOs X Apple clang version 11.0.0 warning: control reaches end of non-void function [-Wreturn-type]
Windows MSVC 2019 16.5.4 Оригинальный пример — error C4716, усложнённый — warning C4715: not all control paths return a value
Оптимизация Program return Console output
Linux x86-x64 Clang 10.0.0
-O0 255 No output
-O1, -O2 No output
Linux x86-x64 gcc 9.3
-O0 89
-O1, -O2, -O3 No output
macOs X Apple clang version 11.0.0
-O0, -O1, -O2
Windows MSVC 2019 16.5.4, оригинальный пример
/Od, /O1, /O2 No build No build
Windows MSVC 2019 16.5.4, усложнённый пример
/Od 41
/O1, /O2 1

Даже в этом простейшем примере четыре компилятора продемонстрировали как минимум три варианта проявления неопределённого поведения.

Идём разбираться, что же там эти компиляторы накомпилировали.

Linux x86-x64 Clang 10.0.0, -O0

Последняя инструкция в функции bad() — ud2.

Описание инструкции из Intel 64 and IA-32 Architectures Software Developer’s Manual:

UD2—Undefined Instruction
Generates an invalid opcode exception. This instruction is provided for software testing to explicitly generate an invalid opcode exception. The opcode for this instruction is reserved for this purpose.
Other than raising the invalid opcode exception, this instruction has no effect on processor state or memory.

Even though it is the execution of the UD2 instruction that causes the invalid opcode exception, the instruction pointer saved by delivery of the exception references the UD2 instruction (and not the following instruction).

This instruction’s operation is the same in non-64-bit modes and 64-bit mode.

Если кратко — это специальная инструкция для генерации исключения.

Надо обернуть вызов bad() в блок try… catch !?

Как бы не так. Это не c++-исключение.

Можно ли отловить ud2 в рантайме?
Под Windows для этого следует использовать __try, под Linux и macOs X — обработчик сигнала SIGILL.

Linux x86-x64 Clang 10.0.0, -O1, -O2

В результате оптимизации компилятор просто взял и выбросил как тело функции bad(), так и её вызов.

Linux x86-x64 gcc 9.3, -O0

Пояснения (в обратном порядке, т.к. в данном случае цепочку проще разбирать с конца):

5. Вызывается оператор вывода в stream для bool (строка 14);

4. В регистр edi помещается адрес std::cout — это первый аргумент оператора вывода в stream (строка 13);

3. В регистр esi помещается содержимое регистра eax — это второй аргумент оператора вывода в stream (строка 12);

2. Обнуляются три старших байта eax, значение al при этом не меняется (строка 11);

1. Вызывается функция bad() (строка 10);

0. Функция bad() должна поместить возвращаемое значение в регистр al.

Вместо этого в строке 4 — nop (No Operation, пустышка).

В консоль выводится один байт мусора из регистра al. Программа завершается штатно.

Linux x86-x64 gcc 9.3, -O1, -O2, -O3

Компилятор всё повыбрасывал в результате оптимизации.

macOs X Apple clang version 11.0.0, -O0

Путь булевского аргумента оператора вывода в поток (на сей раз в прямом порядке):

1. В регистр edx помещается содержимое регистра al (строка 8);

2. Зануляются все биты регистра edx, кроме младшего (строка 9);

3. В регистр rdi помещается указатель на std::cout — это первый аргумент оператора вывода в stream (строка 10);

4. В регистр esi помещается содержимое регистра edx — это второй аргумент оператора вывода в stream (строка 11);

5. Вызывается оператор вывода в stream для bool (строка 13);

Функция main ожидает получить результат выполнения функции bad() из регистра al.

1. В регистр al помещается значение из следующего, ещё не выделенного, байта стека (строка 4);

2. Зануляются все биты регистра al, кроме младшего (строка 5);

В консоль выводится один бит мусора из нераспределённого стека. Так получилось, что при тестовом запуске там оказался ноль.

Читайте также:  Diagbox stream read error launcher

Программа завершается штатно.

macOs X Apple clang version 11.0.0, -O1, -O2

Булевский аргумент оператора вывода в stream обнуляется (строка 5).

Вызов bad() выброшен при оптимизации.

Программа всегда выводит в консоль ноль и завершается штатно.

Windows MSVC 2019 16.5.4, усложнённый пример, /Od

Видно, что функция bad() должна предоставить возвращаемое значение в регистре al.

Значение, возвращённое функцией bad(), помещается сначала на стек, а потом в регистр edx для вывода в stream.

В консоль выводится один байт мусора из регистра al (если чуть точнее — то младший байт результата rand()). Программа завершается штатно.

Windows MSVC 2019 16.5.4, усложнённый пример, /O1, /O2

Компилятор принудительно заинлайнил вызов bad(). Функция main():

  • копирует в ebx один байт из памяти, находящейся по адресу [rsp+30h];
  • в случае, если rand() вернул ноль, копирует единицу из ecx в ebx (строка 11);
  • копирует это же значение в dl (точнее, его младший байт) (строка 13);
  • вызывает функцию вывода в stream, осуществляющую вывод значения dl (строка 14).

В stream выводится один байт мусора из оперативной памяти (из адреса rsp+30h).

Вывод по примеру 1

Оптимизация Program return Console output Причина
Linux x86-x64 Clang 10.0.0
-O0 255 No output ud2
-O1, -O2 No output Вывод в консоль и вызов функции bad() выброшены в результате оптимизации
Linux x86-x64 gcc 9.3
-O0 89 Один байт мусора из регистра al
-O1, -O2, -O3 No output Вывод в консоль и вызов функции bad() выброшены в результате оптимизации
macOs X Apple clang version 11.0.0
-O0 Один бит мусора из оперативной памяти
-O1, -O2 Вызов функции bad() заменён нулём
Windows MSVC 2019 16.5.4, оригинальный пример
/Od, /O1, /O2 No build No build No build
Windows MSVC 2019 16.5.4, усложнённый пример
/Od 41 Один байт мусора из регистра al
/O1, /O2 1 Один байт мусора из оперативной памяти

Как оказалось, компиляторы продемонстрировали не 3, а целых 6 вариантов неопределённого поведения — просто до рассмотрения листингов дизассемблера мы не могли различить некоторые из них.

Пример 1a — управление неопределённым поведением

Попробуем немного порулить неопределённым поведением — повлиять на значение, возвращаемое функцией bad().

Это можно проделать только с теми компиляторами, которые выводят мусор.
Для этого надо подсовывать желаемые значения в те места, из которых компиляторы их будут брать.

Linux x86-x64 gcc 9.3, -O0

Пустая функция bad() не модифицирует значение регистра al, как от неё требует вызывающий код. Таким образом, если мы разместим в al определённое значение до вызова bad(), то ожидаем увидеть именно это значение в качестве результата выполнения bad().

Очевидно, что это можно сделать с помощью вызова любой другой функции, возвращающей bool. Но также это можно сделать с помощью функции, возвращающей, например, unsinged char.

Windows MSVC 2019 16.5.4, /Od

В примере для MSVC функция bad() возвращает младший байт результата rand().

Без модификации функции bad() внешний код может повлиять на возвращаемое ею значение, изменяя результат rand().

Windows MSVC 2019 16.5.4, /O1, /O2

macOs X Apple clang version 11.0.0, -O0

Надо перед вызовом bad() вписать определённое значение в ту ячейку памяти, которая будет на единицу младше вершины стека в момент вызова bad().

Пример предназначен для компиляции с опцией -O0, так что не стоит беспокоиться о сохранности переменной memory. Она не будет выброшена при оптимизации даже несмотря на то, что нигде не используется.

При этом переменная memory должна быть не просто единичным значением, а массивом — иначе компилятор располагает её в регистр процессора, а не на стек, как нам надо.

Пример не является универсальным, т.к. вообще компиляторы могут выделять на стеке больше памяти, чем необходимо для пользовательских переменных — тогда функция putToStack в текущем виде будет промахиваться.

Вроде получилось: удаётся менять выдачу функции bad(), и при этом учитывается только младший бит.

Вывод по примеру 1a

Пример позволил убедиться в корректности трактовки листингов дизассемблера.

Пример 1b — сломанный bool

Ну подууууумаешь, в консоль выведется «41» вместо «1»… Разве это опасно?

Проверять будем на двух компиляторах, предоставивших целый байт мусора.

Windows MSVC 2019 16.5.4, /Od

Неопределённое поведение привело к возникновению булевской переменной, которая ломает как минимум:

  • операторы сравнения булевских значений;
  • хеш-функцию булевского значения.

Windows MSVC 2019 16.5.4, /O1, /O2

badBool1: 213
badBool2: 137
if (badBool1): true
if (!badBool1): false
(badBool1 == true || badBool1 == false || badBool1 == badBool2): false
std::set .size(): 4
std::unordered_set .size(): 4

Работа с испорченной булевской переменной не изменилась при включении оптимизации.

Linux x86-x64 gcc 9.3, -O0

По сравнению с MSVC, в gcc добавилась ещё и некорректная работа оператора not.

Вывод по примеру 1b

Нарушение работы базовых операций с булевскими значениями может иметь серьёзные последствия для высокоуровневой логики.

Почему так произошло?

Потому что некоторые операции с булевскими переменными реализованы в предположении, что true — это строго единица.

В дизассемблере этот вопрос рассматривать не будем — статья и так получилась объёмной.

В очередной раз уточним таблицу с поведением компиляторов:

Оптимизация Program return Console output Причина Последствия использования результата bad()
Linux x86-x64 Clang 10.0.0
-O0 255 No output ud2
-O1, -O2 No output Вывод в консоль и вызов функции bad() выброшены в результате оптимизации
Linux x86-x64 gcc 9.3
-O0 89 Один байт мусора из регистра al Нарушение работы:
not; ==; !=; ; =; std::hash.
-O1, -O2, -O3 No output Вывод в консоль и вызов функции bad() выброшены в результате оптимизации
macOs X Apple clang version 11.0.0
-O0 Один бит мусора из оперативной памяти
-O1, -O2 Вызов функции bad() заменён нулём
Windows MSVC 2019 16.5.4, оригинальный пример
/Od, /O1, /O2 No build No build No build
Windows MSVC 2019 16.5.4, усложнённый пример
/Od 41 Один байт мусора из регистра al Нарушение работы:
==; !=; ; =; std::hash.
/O1, /O2 1 Один байт мусора из оперативной памяти Нарушение работы:
==; !=; ; =; std::hash.

Четыре компилятора дали 7 различных проявлений неопределённого поведения.

Пример 2 — struct

Возьмём пример чуть посложнее:

Структура Test требует для конструирования один параметр типа int. Из её конструктора и деструктора производится вывод диагностических сообщений. Функция bad(int) имеет два корректных пути выполнения, ни один из которых не будет реализован при единственном вызове.

На этот раз — сначала таблица, потом разбор дизассемблера по непонятным пунктам.

Оптимизация Program return Console output Причина
Linux x86-x64 Clang 10.0.0
-O0 255 rnd: 1804289383 ud2
-O1, -O2 rnd: 1804289383
Test::Test(142)
142
Test::

Test() Проверка if (v == 1) не производится. Блок else if превратился в просто else. Linux x86-x64 gcc 9.3 -O0 rnd: 1804289383
4198608
Test::

Test() nop вместо вызова конструктора на некорректном пути выполнения.
value содержит мусор из стека. -O1, -O2, -O3 rnd: 1804289383
Test::Test(142)
142
Test::

Test() Проверка if (v == 1) не производится. Блок else if превратился в просто else. macOs X Apple clang version 11.0.0 -O0 The program has unexpectedly finished. rnd: 16807 ud2 -O1, -O2 rnd: 16807
Test::Test(142)
142
Test::

Test() Проверка if (v == 1) не производится. Блок else if превратился в просто else. Windows MSVC 2019 16.5.4 /Od /RTCs Access violation reading location 0x00000000CCCCCCCC rnd: 41 Побочный эффект MSVC stack frame run-time error checking /Od, /O1, /O2 rnd: 41
8791061810776
Test::

Test() Мусор из ячейки памяти, адрес которой оказался в rax

Опять мы видим множество вариантов: кроме уже известного ud2 есть ещё как минимум 4 разных поведения.

Весьма интересно обращение компиляторов с конструктором:

  • в одних случаях выполнение продолжилось без вызова конструктора — в этом случае объект оказался в каком-то случайном состоянии;
  • в других случаях произошёл вызов конструктора, не предусмотренный на пути выполнения, что довольно странно.

Linux x86-x64 Clang 10.0.0, -O1, -O2

В коде производится только одно сравнение (строка 14), и присутствует только один условный переход (строка 15). Компилятор проигнорировал второе сравнение и второй условный переход.
Это наводит на подозрение, что неопределённое поведение началось раньше, чем предписывает Стандарт.

Но проверка условия второго if не содержит побочных эффектов, и логика компилятора сработала следующим образом:

  • если второе условие окажется верным — надо вызвать конструктор Test с аргументом 142;
  • если второе условие окажется не верным — произойдёт выход из функции без возрата значения, что означает неопределённое поведение, при котором компилятор может сделать всё, что угодно. В том числе — вызвать тот же конструктор с тем же аргументом;
  • проверка является лишней, вызов конструктора Test с аргументом 142 можно производить без проверки условия.

Посмотрим, что произойдёт, если вторая проверка будет содержать условие с побочными эффектами:

Компилятор честно воспроизвёл все положенные побочные эффекты, вызвав rand() (строка 16), чем развеял сомнения о неподобающе раннем начале неопределённого поведения.

Windows MSVC 2019 16.5.4, /Od /RTCs

Опция /RTCs включает stack frame run-time error checking. Эта опция доступна только в debug-сборке. Рассмотрим дизассемблированный код участка main():

Перед вызовом bad(int) (строка 4) производится подготовка аргументов — в регистр edx копируется значение переменной rnd (строка 2), и в регистр rcx загружается эффективный адрес какой-то локальной переменной, расположенной по адресу rsp+28h (строка 3).

Предположительно, rsp+28 — адрес временной переменной, хранящей результат вызова bad(int).

Это предположение подтверждается строками 19 и 20 — эффективный адрес этой же переменной загружается в rcx, после чего вызывается деструктор.

Однако в интервале строк 4 — 18 к этой переменной нет обращения, несмотря на вывод в stream значения её поля данных.

Как мы видели из прошлых листингов MSVC, аргумент для оператора вывода в поток следует ожидать в регистре rdx. В регистр rdx попадает результат разыменования адреса, находящегося в rax (строка 9).

Таким образом, вызывающий код ожидает от bad(int):

  • заполнения переменной, адрес которой передан через регистр rcx (тут мы видим RVO в действии);
  • возврат адреса этой переменной через регистр rax.

Переходим к рассмотрению листинга bad(int):

  • в eax заносится значение 0xCCCCCCCC, которое мы видели в сообщении Access violation (строка 9) (обратите внимание — только 4 байта, в то время как в сообщении AccessViolation адрес состоит из 8 байт);
  • вызывается команда rep stos, осуществляющая 0xC циклов записи содержимого eax в память начиная с адреса rdi (строка 10). Это 48 байтов — ровно столько, сколько выделено на стеке в строке 6;
  • на корректных путях выполнения в rax заносится значение из rsp+40h (строки 23, 36);
  • значение регистра rcx (через который main() передал адрес назначения) помещается на стек по адресу rsp+8 (строка 4);
  • в стек впихивается rdi, что приводит к уменьшению rsp на 8 (строка 5);
  • на стеке выделяется 30h байт путём уменьшению rsp (строка 6).

Таким образом, rsp+8 в строке 4 и rsp+40h в остальной части кода — одно и то же значение.
Код довольно запутанный, т.к. в нём не применяется rbp.

В сообщении Access Violation есть целых две случайности:

  • нули в старшей части адреса — там мог быть любой мусор;
  • адрес случайно оказался некорректным.

Судя по всему, опция /RTCs включила затирание стека определёнными ненулевыми значениями, а сообщение Access Violation — лишь случайный побочный эффект.

Посмотрим, чем отличается код со включённой опцией /RTCs от кода без неё.

Код участков main() отличается только адресами локальных переменных на стеке.

(для наглядности я разместил рядом два варианта функции bad(int) — с /RTCs и без)
Без /RTCs исчезла инструкция rep stos и подготовка аргументов для неё в начале функции.

Пример 2a

Снова попробуем поуправлять неопределённым поведением. На этот раз только для одного компилятора.

Windows MSVC 2019 16.5.4, /Od /RTCs

С опцией /RTCs компилятор вставляет в начало функции bad(int) код, заполняющий младшую половину rax фиксированным значеним, что может приводить к Access violation.

Чтобы изменить это поведение, достаточно заполнить rax каким-либо корректным адресом.
Этого можно добиться очень простой модификацией: добавить в тело bad(int) вывод чего-нибудь в std::cout.

Источник

Smartadm.ru
Adblock
detector