Исключения В Windows X64. Как Это Работает. Часть 4

По материалам, описанным в первый , второй И третий В некоторых частях этой статьи мы продолжим обсуждать тему обработки исключений в Windows x64. Описанный материал требует знания основных понятий, таких как пролог, эпилог, функциональный фрейм, а также понимания основных процессов, таких как действия пролога и эпилога, передача параметров функции и возврат результата функции.

Если читатель не знаком с вышеизложенным, то перед прочтением рекомендуется ознакомиться с материалом из первой части данной статьи.

Если читатель не знаком со структурами PE-образов, участвующими в процессе обработки исключений, то перед чтением рекомендуется ознакомиться с материалом второй части данной статьи.

Также, если читатель не знаком с процессом поиска и вызова обработчиков исключений, ему рекомендуется прочитать третью часть этой статьи.

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

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

Статья сопровождается реализацией механизма, которая находится в папке исключений git-репозитория.

этот адрес .



1. Продвижение стека

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

Те.

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

На рисунке 1 показан пример такой ситуации, где стрелка указывает направление роста стека.



Исключения в Windows x64. Как это работает. Часть 4

Изображение 1 В приведенном выше примере стек состоит из четырех функциональных фреймов, где Main называется Func1, Func1 называется Func2, а Func2 называется Func3. Так, например, если Func3 необходимо вернуть управление Main, то он будет использовать функцию RtlUnwind/RtlUnwindEx, которая экспортируется модулем ntdll.dll в пространство пользователя и модулем ntoskrnl.exe в пространство ядра.

Прототип функции RtlUnwindEx показан ниже на рисунке 2.

Исключения в Windows x64. Как это работает. Часть 4

фигура 2 Параметр TargetFrame принимает адрес кадра функции, на которую следует раскрутить стек.

Параметр TargetIp принимает адрес инструкции, с которой продолжится выполнение после раскрутки.

Параметр ExceptionRecord принимает указатель на структуру EXCEPTION_RECORD, которая будет передана обработчикам при раскрутке.

Параметр ReturnValue записывается в регистр RAX процессора, т.е.

сразу после передачи управления соответствующей функции регистр RAX будет содержать значение этого параметра.

Параметр ContextRecord содержит указатель на структуру CONTEXT, которая используется функцией RtlUnwindEx при раскрутке функций и определении целевого состояния процессора после раскрутки.

Параметр HistoryTable принимает указатель на структуру, которая используется для кэширования поиска.

Формат этой структуры можно найти в winnt.h. Параметр TargetFrame является необязательным.

Если его значение равно NULL, то функция RtlUnwindEx выполняет так называемую выходную размотку, при которой разматываются кадры всех функций стека.

В этом случае параметр TargetIp игнорируется.

Параметр ExceptionRecord является необязательным, и если он равен NULL, то функция RtlUnwindEx инициализирует свою структуру EXCEPTION_RECORD, где поле ExceptionCode будет содержать значение STATUS_UNWIND, поле ExceptionRecord будет содержать NULL, поле ExceptionAddress будет содержать указатель на функцию RtlUnwindEx Инструкция, а поле NumberParameters будет содержать 0. Параметр HistoryTable является необязательным.

Прототип функции RtlUnwind отличается только тем, что не принимает два последних параметра.

Ниже, на рисунке 3, показан пример работы функции RtlUnwind.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 3 На рисунке выше показан пример программы, состоящей из четырех функций: _tmain, Func1, Func2, Func3. Функция _tmain вызывает Func1, Func1 вызывает Func2, а Func2 вызывает Func3. Функции Func1, Func2, Func3 возвращают логическое значение.

Функция Func3 выполняет виртуальную раскрутку трех предыдущих функций, чтобы: найти адрес кадра функции _tmain; найдите адрес инструкции, с которой продолжится выполнение, и в этом примере адрес будет указывать на инструкцию сразу после инструкции, вызывающей функцию Func1. Справа от исходного кода находится ассемблерный код функций _tmain и Func3, адреса инструкций которых абсолютны.

Справа от ассемблерного кода показаны состояния процессора и стек вызовов для трех случаев: вверху — состояние процессора и стек вызовов непосредственно перед вызовом функции Func1; в середине показано состояние процессора и стек вызовов непосредственно перед вызовом функции RtlUnwind; Ниже показано состояние процессора после выполнения функции RtlUnwind. Указатели инструкций этих состояний сопоставляются с инструкциями ассемблера с использованием уникальных номеров.

Следует обратить внимание на последний случай, когда регистр RAX принял значение параметра ReturnValue, а вызов стека сократился до одной функции, т.е.

рамки функций Func1, Func2 и Func3 больше не существуют в стеке.

Поскольку значение RAX после раскрутки не равно нулю, функция _tmain выведет сообщение на экран.

В обычном случае, т. е.

если продвижение не было выполнено, это сообщение не будет отображаться, поскольку функция Func3 возвращает false. Также следует обратить внимание на то, что цикл поиска указателя кадра функции _tmain выполняет четыре итерации, когда нужно раскрутить только три функции.

Это связано с ранее обсуждавшимися особенностями функции RtlVirtualUnwind. Дело в том, что после вызова функции RtlVirtualUnwind параметры HandlerData и SettingerFrame примут соответствующие значения для функции, для которой производилась виртуальная размотка, тогда как параметр ContextRecord будет отражать состояние процессора сразу после вызова функции размотки.

.

Поэтому на третьей итерации цикла функция RtlVirtualUnwind вернет указатель кадра для функции Func1 в параметр SettingerFrame, тогда как параметр ContextRecord будет отражать состояние процессора сразу после вызова функции Func1. Поэтому требуется дополнительная итерация для определения указателя фрейма функции _tmain. Функция RtlUnwind/RtlUnwindEx также перед раскруткой стека последовательно вызывает обработчики раскрутки всех функций, начиная с себя и до функции, являющейся целевой, включительно.

Поскольку функция RtlUnwind/RtlUnwindEx не имеет обработчиков исключений/размотки, она будет просто пропущена во время процесса виртуальной размотки и, следовательно, побочных эффектов не будет. С другой стороны, это накладные расходы, т.к.

для нахождения функционального фрейма, вызвавшего функцию RtlUnwind/RtlUnwindEx, необходимо выполнить дополнительную виртуальную размотку.

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

Ниже на рисунке 4 представлена блок-схема функции RtlUnwindEx.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 4 В начале своей работы функция получает нижний и верхний пределы стека.

Далее функция фиксирует текущее состояние процессора, вызывая функцию RtlCaptureContext. Таким образом, структура CONTEXT будет отражать состояние процессора сразу после вызова функции RtlCaptureContext. Эта же структура используется как начальное состояние процессора, с которого начинается виртуальное продвижение функций.

Функция RtlUnwindEx при своей работе использует две структуры КОНТЕКСТ: одна отражает состояние процессора в момент выполнения функции, для которой вызывается обработчик (далее – текущий контекст); другой отражает состояние процессора сразу после возврата из этой функции (далее — предыдущий контекст).

Это необходимо из-за ранее обсуждавшихся особенностей функции RtlVirtualUnwind. Также функция RtlUnwindEx, как было указано ранее, инициализирует структуру EXCEPTION_RECORD для последующей передачи в обработчики раскрутки, если при вызове функции не был передан соответствующий параметр.

Далее функция генерирует начальное значение поля ExceptionFlags для структуры EXCEPTION_RECORD. Это значение хранится в локальной переменной и изначально не сохраняется в поле самой структуры.

Функция устанавливает флаг EXCEPTION_UNWINDING, и если в функцию не был передан адрес кадра целевой функции, то функция также устанавливает флаг EXCEPTION_EXIT_UNWIND. Таким образом, флаг EXCEPTION_UNWINDING для обработчиков означает, что раскручиваются кадры всех функций, а флаг EXCEPTION_EXIT_UNWIND означает, что раскручиваются кадры всех функций.

Далее функция с помощью функции RtlLookupFunctionEntry получает адрес PE-образа и указатель на структуру RUNTIME_FUNCTION функции этого образа, обработчик которой необходимо вызвать (далее – текущая функция).

Адрес одной из инструкций этой функции извлекается из текущего контекста.

На первой итерации это будет адрес инструкции самой функции RtlUnwindEx. Если функция RtlLookupFunctionEntry не возвращает указатель, то текущая функция, для которой была предпринята попытка ее обработчика, считается простой функцией, и поэтому функция не имеет фрейма.

Поскольку простые функции не выделяют память в стеке, их значение RSP будет указывать на адрес возврата, поэтому для таких функций функция RtlUnwindEx извлекает этот адрес, копирует его значение в текущий контекст и увеличивает значение Rsp текущего контекста.

поле на 8. Теперь текущий контекст отражает состояние процессора в момент выполнения следующей функции в стеке выше.

Далее функция продолжит свою работу, начиная с получения адреса PE-образа и указателя на структуру RUNTIME_FUNCTION, для адреса новой инструкции, для следующей функции в стеке выше.

Для функций фрейма функция RtlLookupFunctionEntry вернет указатель на структуру RUNTIME_FUNCTION. В этом случае вызывается функция RtlVirtualUnwind для определения указателя кадра текущей функции, а также адреса ее обработчика и указателя на данные для этого обработчика.

Перед вызовом функции RtlVirtualUnwind функция RtlUnwindEx скопирует текущий контекст в предыдущий.

Это сделано для того, чтобы сохранить состояние процессора, описывающее момент выполнения текущей функции, на случай, если функция окажется целевой.

Уже несколько раз упоминалось, что функция RtlVirtualUnwind возвращает адрес кадра функции, которая была выполнена в переданном состоянии процессора, когда при возврате из функции RtlVirtualUnwind состояние будет описывать следующую функцию в стеке выше.

Следовательно, когда функции RtlUnwindEx потребуется возобновить выполнение целевой функции, будет невозможно использовать состояние процессора, возвращаемое функцией RtlVirtualUnwind, поскольку оно будет отражать выполнение функции, вызвавшей целевую функцию.

Сразу после вызова функции RtlVirtualUnwind функция RtlUnwindEx проверит указатель кадра разматываемой функции, чтобы увидеть, не превышает ли он предел стека.

Функция также проверит, находится ли кадр текущей функции выше в стеке, чем кадр целевой функции, что, в свою очередь, будет означать, что функция RtlUnwindEx пропустила кадр целевой функции из-за повреждения стека, повреждения раздел .

pdata и т. д. В обоих случаях функция выдаст исключение STATUS_BAD_STACK. В противном случае, если функция RtlVirtualUnwind не вернула адрес обработчика, функция RtlUnwindEx поменяет местами текущий и предыдущий контексты, если текущая функция не была целевой.

Таким образом, следующая функция выше в стеке станет текущей.

Далее функция продолжит свою работу, начиная с получения адреса PE-образа и указателя на структуру RUNTIME_FUNCTION, для адреса новой инструкции, для следующей функции в стеке выше.

Если функция RtlVirtualUnwind вернула адрес обработчика текущей функции, то необходимо вызвать ее обработчик.

Перед вызовом функция RtlUnwindEx установит флаг EXCEPTION_TARGET_UNWIND, если текущая функция является целевой.

Таким образом, обработчик функции сможет определить, что соответствующая ему функция — это функция, управление которой передается.

Затем функция RtlUnwindEx обновит содержимое поля ExceptionFlags структуры EXCEPTION_RECORD из ее локальной копии.

Обработчик исключений впервые обсуждался в разделе 3 части 2 этой статьи, а его прототип показан на рисунке 5. Перед вызовом обработчика функция, как и функция RtlDispatchException, рассмотренная в разделе 2.2 части 3 этой статьи, подготавливает Структура DISPATCHER_CONTEXT, которая активно используется в случаях вложенных исключений (nestedException) и активного продвижения (collied unwind).

Определение самой структуры также изображено на рисунке 17 в разделе 2.2 третьей части данной статьи.

Поля этой структуры инициализируются так же, как и в случае с функцией RtlDispatchException, за исключением того, что поле TargetIp будет содержать значение соответствующего параметра, переданного в функцию RtlUnwindEx, т.е.

адрес инструкции, из которой выполняется выполнение.

будет возобновлено после раскручивания; поле ContextRecord будет содержать указатель на структуру CONTEXT, описывающую состояние процессора в момент выполнения текущей функции, а не следующей выше по стеку; Поле ScopeIndex содержит текущее значение локальной переменной и будет обсуждаться более подробно при обсуждении конструкций try/Exception и try/finally. Обработчик, как и функция RtlDispatchException, не вызывается напрямую, а вместо этого используется вспомогательная функция RtlpExecuteHandlerForUnwind, которая принимает те же параметры, что и сам обработчик, и также возвращает то же значение.

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

Ассемблерное представление функции показано ниже на рисунке 5.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 5 Как показано на рисунке, функция сначала выделяет память в стеке для переменных регистра и одной переменной, сохраняет в этой переменной указатель на переданную структуру DISPATCHER_CONTEXT и вызывает обработчик исключений, адрес которого хранится в поле LanguageHandler функции структура DISPATCHER_CONTEXT. Также обратите внимание на наличие заполнителя тела функции.

Ее роль такая же, как и у функции RtlpExecuteHandlerForException. Представление функции обработчика исключений на языке ассемблера показано ниже на рисунке 6.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 6 Как показано на рисунке, обработчик копирует контекст предыдущего процесса очистки в структуру DISPATCHER_CONTEXT текущего процесса поиска или очистки обработчика.

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

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

Также следует отметить, что вызов обработчиков возобновляется из той функции, на которой процесс раскрутки был прерван.

Те.

для таких функций обработчик будет вызываться снова.

Это будет объяснено более подробно при обсуждении конструкций try/Exception и try/finally. После подготовки структуры DISPATCHER_CONTEXT функция RtlUnwindEx вызывает соответствующий обработчик.

Сразу после вызова обработчика функция сбрасывает флаги EXCEPTION_COLLIDED_UNWIND и EXCEPTION_TARGET_UNWIND. Если обработчик вернул ExceptionContinueSearch, то функция поменяет местами текущий и предыдущий контексты, если текущая функция не была целевой.

Таким образом, следующая функция выше в стеке станет текущей.

Далее функция продолжит свою работу, начиная с получения адреса PE-образа и указателя на структуру RUNTIME_FUNCTION, для адреса новой инструкции, для следующей функции в стеке выше.

Если обработчик вернул ExceptionCollidedUnwind, то это означает, что в процессе раскрутки была обнаружена еще одна активная раскрутка, в контексте которой произошло исключение.

В этом случае структура DISPATCHER_CONTEXT функции RtlUnwindEx будет содержать контекст прерванной размотки, поскольку он был скопирован обработчиком функции RtlpExecuteHandlerForUnwind. Следовательно, функция обновит текущий контекст из поля ContextRecord структуры DISPATCHER_CONTEXT, получит предыдущий с помощью функции RtlVirtualUnwind, установит флаг EXCEPTION_COLLIDED_UNWIND и вызовет обработчик, в контексте которого ранее возникло исключение и в зависимости от его возвращаемого значения.

результат, выполните ранее описанные действия.

Во всех остальных случаях функция RtlUnwindEx выдаст исключение STATUS_INVALID_DISPOSITION. На каждой итерации, прежде чем получить адрес PE-образа и указатель на структуру RUNTIME_FUNCTION, функция с помощью функции RtlpIsFrameInBounds проверяет, находится ли указатель кадра функции, для которой была предпринята попытка вызвать ее обработчик, в пределах лимита стека.

и не является указателем кадра целевой функции.

Если такая проверка дает положительный результат, то функция продолжается.

В противном случае, если указатель кадра находится вне предела и указатель не является адресом кадра целевой функции, то либо была произведена размотка при выходе, и при размотке ни один из обработчиков не остановил этот процесс, либо указатель кадра функции не был обнаружено из-за повреждения стека, повреждения разделов .

pdata и т. д. В этом случае функция RtlUnwindEx выдаст исключение, чтобы разрешить отладку, но не обработать ее.

Во всех остальных случаях функция завершится, поскольку найден целевой кадр функции.

В этом случае поле Rax текущего контекста будет содержать значение переданного параметра ReturnValue, а поле Rip того же контекста будет содержать значение переданного параметра TargetIp, если только код исключения не STATUS_UNWIND_CONSOLIDATE. Т.

к.

этот случай не имеет прямого отношения к обсуждаемой теме; этот код не будет обсуждаться в этой статье.

Единственное, что здесь следует отметить, это то, что раскрутка с этим кодом приведет к вызову функции RtlRestoreContext перед возобновлением, и если поле Rip обновится, обработчик получит неверное представление о состоянии процессора.

Далее функция RtlUnwindEx вызывает функцию RtlRestoreContext, которой передает два параметра: текущий контекст и указатель на структуру EXCEPTION_RECORD, которая либо была передана в функцию RtlUnwindEx, либо передается указатель на локально сгенерированную структуру.

К моменту вызова функции RtlRestoreContext были вызваны обработчики очистки для всех функций в стеке, начиная с вершины стека и до целевой функции включительно.

Функция RtlRestoreContext не возвращает управление, поскольку применяет к процессору новое состояние.

Стоит отметить, что проверка указателя кадра на каждой итерации является ошибкой реализации, поскольку должен проверяться указатель стека из текущего контекста, а не указатель кадра.

Прежде всего, эта проверка производится сразу после виртуального продвижения текущей функции.

А если результат проверки отрицательный, то функция выдаст исключение.

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

Эта ошибка все еще сохраняется.



2. Конструкции Try/Exception и try/finally.

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

Конструкции try/Exception и try/finally — это механизм, который позволяет размещать код обработки исключений непосредственно в теле функции во время разработки.

Следовательно, поскольку код обработки размещается непосредственно в теле, эти части кода не могут быть вызваны операционной системой напрямую.

Чтобы обеспечить корректное функционирование этих конструкций, компилятор генерирует вспомогательную информацию, которая используется обработчиками исключений, вызываемыми операционной системой.

Ранее упоминалось, что всю обработку исключений можно разделить на две фазы.

Фаза поиска и передачи управления обработчикам исключений обсуждаемых структур — вторая фаза.

Такое разделение необходимо, поскольку разные языки программирования по-разному обрабатывают исключения; таким образом, сама операционная система абстрагируется от понимания разнообразия механизмов разных языков программирования.

Компилятор C/C++ резервирует функцию __C_specific_handler. Именно эта функция отвечает за поиск и передачу управления соответствующей структуре.

Сама функция должна быть реализована программистом.

Такой подход позволяет компилятору абстрагироваться от понимания работы самой операционной системы и адаптировать исполняемый образ к любой среде исполнения, например, к подсистеме Win32, к среде исполнения ядра Windows или к любой другой среде.

Кроме того, реализация этой функции экспортируется модулем ntdll.dll в пространство пользователя и модулем ntoskrnl.exe в пространство ядра.

Поставляемые Windows SDK и WDK содержат библиотеки, которые импортируют эту функциональность из соответствующего модуля.

Поле ExceptionHandlerAddress структуры EXCEPTION_HANDLER будет содержать указатель на эту функцию, тогда как поле LanguageSpecificData той же структуры будет содержать структуру SCOPE_TABLE, описывающую расположение всех конструкций в теле функции.

Прототип функции показан на рисунке 5 в разделе 3 второй части статьи.

Определение структуры SCOPE_TABLE представлено ниже на рисунке 7.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 7 Поле Count содержит количество структур в теле функции и, следовательно, количество элементов ScopeRecord в структуре.

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

Элементы ScopeRecord сортируются в следующем порядке: невложенные конструкции следуют друг за другом в том порядке, в котором они появляются в коде, а вложенные конструкции всегда располагаются перед конструкцией, в которую они вложены.

Поле BeginAddress элемента ScopeRecord содержит адрес начала блока try. Поле EndAddress содержит адрес инструкции, следующей за последней инструкцией, заключенной в блоке try. Поле JumpTarget, если оно не равно нулю, содержит адрес первой кодовой инструкции, заключенной в блоке исключений.

Код блока исключений идет сразу после кода, заключенного в блоке try. Поле HandlerAddress содержит адрес функции фильтра кроме блоков.

Хотя фильтр исключений заключен в круглые скобки после выражения исключения, код фильтра генерируется компилятором как отдельная функция, прототип которой показан ниже на рисунке 8.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 8 Функция принимает два параметра.

Первый параметр содержит указатель на структуру, определение которой показано ниже на рисунке 9. Второй параметр содержит указатель на функциональный фрейм, в котором находится соответствующая структура.

Этот указатель используется фильтром, если при фильтрации необходимо обратиться к локальным переменным функции, в которой находится соответствующая конструкция.



Исключения в Windows x64. Как это работает. Часть 4

Рисунок 9 Как показано на рисунке выше, структура содержит два указателя.

Первый указывает на структуру, описывающую причину исключения, второй указывает на структуру, описывающую состояние процессора в момент возникновения исключения.

Функция фильтра возвращает следующие значения: EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH, EXCEPTION_CONTINUE_EXECUTION. Первое значение означает, что вы хотите передать управление обработчику исключений, для которого была вызвана функция фильтра.

Это значение также можно закодировать непосредственно в поле HandlerAddress. В этом случае конструкция не имеет фильтра и управление всегда передается обработчику исключений этой конструкции.

Второе значение указывает, что поиск обработчика исключений следует продолжить.

Третье значение означает, что поиск следует прервать и прерванный поток должен возобновить выполнение.

Если поле JumpTarget равно нулю, то конструкция является конструкциейfinally, и код, заключенный в блокfinally, следует сразу за кодом, заключенным в блоке try. В этом случае поле HandlerAddress содержит адрес функции, которая по своему содержанию повторяет код, заключенный в блокеfinally. Прототип этой функции показан на рисунке 10.

Исключения в Windows x64. Как это работает. Часть 4

Рисунок 10 Поскольку код, заключенный в блокfinally, выполняется независимо от того, возникло исключение или нет, то в случае возникновения исключения этот код нельзя вызвать напрямую, поскольку он находится в теле функции.

А поскольку этот код необходимо вызывать при раскрутке, компилятор дублирует код, заключенный в блокеfinally, в отдельную функцию.

Первый параметр представляет собой логическое значение, указывающее, что код блокаfinally выполняется, поскольку код, заключенный в блоке try, завершился ненормально (то есть во время его выполнения произошло исключение).

Второй параметр содержит указатель на фрейм функции, в котором находится соответствующая конструкция.

Этот указатель используется функцией так же, как и функция фильтра исключений — для доступа к локальным переменным функции, в которой находится соответствующая конструкция.

Функция не возвращает никаких значений.

В тех случаях, когда во время выполнения кода, заключенного в блоке try, не возникает никаких исключений, выполняется код блокаfinally, следующий сразу за кодом блока try. Все адреса в структуре SCOPE_TABLE являются адресами относительно начала изображения.

Ниже, на рисунке 11, показан пример структуры SCOPE_TABLE, которую сгенерирует компилятор.



Исключения в Windows x64. Как это работает. Часть 4

Рисунок 11 На рисунке выше показан пример программы, функция _tmain которой включает конструкции try/Exception и try/finally. Слева от исходного кода находится ассемблерное представление функций: _tmain, функции фильтра нижней конструкции try/Exception и функции, дублирующие код, заключенный в блокеfinally. Функции перечислены снизу вверх.

Адреса инструкций ассемблера абсолютны.

Зеленые маркеры сравнивают код, заключенный в блоки, с его ассемблерными эквивалентами.

Обратите внимание, что блок кода с маркером 2 появляется в ассемблерном представлении дважды: в теле функции _tmain и в самой верхней функции.

Последний является дубликатом кода, заключенного в блокеfinally. Также следует обратить внимание на наличие оператора nop после оператора вызова функции FailExecution в блоке кода с маркером 1. Этот оператор также является заполнителем, как и в случае с функциями шлюза, функцией RtlpExecuteHandlerForException и функцией RtlpExecuteHandlerForUnwind. Если наполнитель отсутствует, то при проверке инструкции на принадлежность к той или иной конструкции может быть сделано ошибочное предположение о ее принадлежности.

В этом случае будет сделано ошибочное предположение, что инструкция вызова функции FailExecution не принадлежит блоку кода с маркером 1, поскольку функция RtlVirtualUnwind после раскрутки вернет адрес не инструкции, вызывающей функцию FailExecution. , а к инструкции сразу после нее.

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

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

Вверху находится элемент таблицы функций, ниже — структура UNWIND_INFO, на которую ссылается этот элемент. Несмотря на то, что структура EXCEPTION_HANDLER не является частью структуры UNWIND_INFO, на рисунке она показана как часть этой структуры, поскольку, если она присутствует, она следует сразу после структуры UNWIND_INFO. Ниже структуры UNWIND_INFO находится более подробное представление структуры EXCEPTION_HANDLER, а ниже — более подробное представление поля LanguageSpecificData этой структуры, в котором находится структура SCOPE_TABLE. В самом низу последовательно показаны элементы ScopeRecord массива этой структуры.

Все адреса в сгенерированных структурах относительны.

Теги: #C++ #C++ #Windows #Системное программирование #UEFI #github #Exceptions #Exceptions #try/Exception #open source #C++ #Системное программирование

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.