Ошибка создания глобального объекта в памяти
При написании приложений, одной из важнейших вопросов являются потребление памяти и отзывчивость (скорость работы).
Считается, что сборщик мусора – черный ящик, работу которого нельзя предугадать.
А я скажу как бы ни так!
Управление политикой
Для того чтобы этот параметр политики вступил в силу, не требуется перезапуск устройства.
Изменения прав пользователя вступают в силу при его следующем входе в учетную запись.
Location
Computer Configuration\Windows Settings\Security Settings\Local Policies\User Rights Assignment
Рекомендации
- Не назначать учетным записям пользователей это право.
▌ Структура организации размещения объектов в памяти
Однажды я уже писал про определение размеров CLR-объектов. Чтобы не пересказывать статью, давайте лишь вспомним основные моменты.
Ссылка живет в стеке при выполнении любого метода, либо живет в поле какого-либо класса.
Вы не можете создать объект в вакууме без создания ссылки.
Чтобы не было спекуляций по поводу размеров объектов и проведения каких-либо тестов с помощью SOS (Son of Strike), измерения GC.TotalMemory и т.п. — просто посмотрим на исходники CLR, а точнее Shared Source Common Language Infrastructure 2.0, являющийся своего рода исследовательским проектом.
Каждый тип имеет свой MethodTable, и все экземпляры объектов одного и того же типа ссылаются на один и тот же MethodTable. Данная таблица хранит информацию о самом типе (интерфейс, абстрактный класс и т.д.).
Каждый объект содержит два дополнительных поля – заголовок объекта, в котором хранится адрес SyncTableEntry (запись syncblk), и Method Table Pointer (TypeHandle).
SyncTableEntry – структура, хранящая ссылку на CLR-объект и ссылку на сам SyncBlock.
SyncBlock – структура данных, в которой хранится хеш-код для любого объекта.
Говоря «для любого» значит, что CLR заранее инициализирует определенное количество SyncBlock’ов. Далее при вызове GetHashCode(), либо Monitor.Enter() среда просто вставляет в заголовок объекта указатель на уже готовый SyncBlock, попутно вычисляя хеш-код.
Делается это вызовом метода GetSyncBlock (смотрим файл %папка с архивом%\sscli20\clr\src\vm\syncblk.cpp) . В теле метода мы можем видеть следующий код:
Метод System.Object.GetHashCode полагается на структуру SyncBlock вызывая метод SyncBlock::GetHashCode.
Первоначальное значение syncblk равно 0 для CLR 2.0, но начиная с CLR 4.0 значение равняется -1.
При вызове Monitor.Exit() syncblk опять становится равной -1.
Хочется также заметить, что массив SyncBlock’ов хранится в отдельной памяти, недоступной GC.
Как же так? Спросите Вы.
Ответ прост – слабые ссылки. CLR создает слабую ссылку на запись в SyncBlock массиве. Когда CLR-объект умирает, SyncBlock обновляется.
Реализация метода Monitor.Enter() зависит от платформы и самого JIT. Так псевдонимом для данного метода в исходниках SSCLI является JIT_MonEnter.
Возвращаясь к теме размещения объектов в памяти и их размеров, хочется вспомнить, что любой экземпляр объекта (пустой класс) занимает минимум 12 байт в x86, а в x64 уже 24 байта.
Убедимся в этом без запуска SOS.
Переходим в файл %папка с архивом%\sscli20\clr\src\vm\object.h
В комментариях к той статье про размеры CLR-объектов меня упрекнули в неточности расчета размера System.String без каких-либо доказательств.
Однако я больше доверяю цифрам и … исходному коду!
Empty в расчет не берем, т.к. это пустая статичная строка.
m_stringLength указывает длину строки.
m_firstChar является указателем (. ) на начало хранения массива юникодных символов, а не первым символом в массиве.
Здесь не используется какая-либо магия – CLR просто находит оффсет.
Чтобы в этом убедиться снова открываем файл %папка с архивом%\sscli20\clr\src\vm\object.h
В самом начале файла видим комментарии к коду:
Вот это и есть внутренняя структура хранения строковых данных.
Далее находим класс StringObject и его метод GetBuffer().
Что ж, буфер (он же массив символов) просто вычисляется оффсетом.
А как же обстоит дело с самим System.String?
Открываем файл %папка с архивом%\sscli20\clr\src\bcl\system\string.cs
Видим следующие строки:
Однако System.String в своей работе полагается на COMString, реализующим сам конструктор, а также многие методы (PadLeft и т.п.).
Чтобы правильно сопоставить названия методов из фреймворка и внутренних C++ реализаций советую посмотреть файл %папка с архивом%\sscli20\clr\src\vm\ecall.cpp
Ну и чтобы окончательно убедиться в том, что m_firstChar является указателем, рассмотрим, например, часть кода метода Join:
Немного другой вариант с подсчетом (но с такими же результатами) приводит знаменитый Jon Skeet.
Перед тем как двигаться вперед, хотелось бы вспомнить и про стек.
Стек – контейнер, создаваемый средой при каждом вызове любого метода. В нем хранятся все данные, необходимые для завершения вызова (адреса локальных переменных, параметры и т.д.).
Таким образом, вызовы дерево вызовов представляют собой FIFO-контейнер, состоящий из стеков. При завершении вызова текущего метода, стек очищается и уничтожается, возвращая управление родительской ветке.
Как я уже писал выше, для переменных ссылочных типов, в стек помещается значение фиксированного размера (4 байта, например, для x86, тип DWORD), содержащее адрес экземпляра объекта, созданного в обычной куче.
В стеке по-умолчанию размещаются экземпляры примитивных типов, не участвующих в упаковке (boxing).
Однако при некоторых оптимизациях JIT- может сразу же расположить значения переменных в регистрах процессора, минуя ОЗУ.
Вспомним, что такое регистр процессора — блок ячеек памяти, образующий сверхбыструю оперативную память внутри процессора, использующаяся самим процессором и большой частью недоступен программисту.
Чем больше кэш CPU, тем более высокую производительность Вы можете получить, независимо от программной платформы.
▌ Структура организации размещения объектов в памяти
Однажды я уже писал про определение размеров CLR-объектов. Чтобы не пересказывать статью, давайте лишь вспомним основные моменты.
Ссылка живет в стеке при выполнении любого метода, либо живет в поле какого-либо класса.
Вы не можете создать объект в вакууме без создания ссылки.
Чтобы не было спекуляций по поводу размеров объектов и проведения каких-либо тестов с помощью SOS (Son of Strike), измерения GC.TotalMemory и т.п. — просто посмотрим на исходники CLR, а точнее Shared Source Common Language Infrastructure 2.0, являющийся своего рода исследовательским проектом.
Каждый тип имеет свой MethodTable, и все экземпляры объектов одного и того же типа ссылаются на один и тот же MethodTable. Данная таблица хранит информацию о самом типе (интерфейс, абстрактный класс и т.д.).
Каждый объект содержит два дополнительных поля – заголовок объекта, в котором хранится адрес SyncTableEntry (запись syncblk), и Method Table Pointer (TypeHandle).
SyncTableEntry – структура, хранящая ссылку на CLR-объект и ссылку на сам SyncBlock.
SyncBlock – структура данных, в которой хранится хеш-код для любого объекта.
Говоря «для любого» значит, что CLR заранее инициализирует определенное количество SyncBlock’ов. Далее при вызове GetHashCode(), либо Monitor.Enter() среда просто вставляет в заголовок объекта указатель на уже готовый SyncBlock, попутно вычисляя хеш-код.
Делается это вызовом метода GetSyncBlock (смотрим файл %папка с архивом%\sscli20\clr\src\vm\syncblk.cpp) . В теле метода мы можем видеть следующий код:
Метод System.Object.GetHashCode полагается на структуру SyncBlock вызывая метод SyncBlock::GetHashCode.
Первоначальное значение syncblk равно 0 для CLR 2.0, но начиная с CLR 4.0 значение равняется -1.
При вызове Monitor.Exit() syncblk опять становится равной -1.
Хочется также заметить, что массив SyncBlock’ов хранится в отдельной памяти, недоступной GC.
Как же так? Спросите Вы.
Ответ прост – слабые ссылки. CLR создает слабую ссылку на запись в SyncBlock массиве. Когда CLR-объект умирает, SyncBlock обновляется.
Реализация метода Monitor.Enter() зависит от платформы и самого JIT. Так псевдонимом для данного метода в исходниках SSCLI является JIT_MonEnter.
Возвращаясь к теме размещения объектов в памяти и их размеров, хочется вспомнить, что любой экземпляр объекта (пустой класс) занимает минимум 12 байт в x86, а в x64 уже 24 байта.
Убедимся в этом без запуска SOS.
Переходим в файл %папка с архивом%\sscli20\clr\src\vm\object.h
В комментариях к той статье про размеры CLR-объектов меня упрекнули в неточности расчета размера System.String без каких-либо доказательств.
Однако я больше доверяю цифрам и … исходному коду!
Empty в расчет не берем, т.к. это пустая статичная строка.
m_stringLength указывает длину строки.
m_firstChar является указателем (. ) на начало хранения массива юникодных символов, а не первым символом в массиве.
Здесь не используется какая-либо магия – CLR просто находит оффсет.
Чтобы в этом убедиться снова открываем файл %папка с архивом%\sscli20\clr\src\vm\object.h
В самом начале файла видим комментарии к коду:
Вот это и есть внутренняя структура хранения строковых данных.
Далее находим класс StringObject и его метод GetBuffer().
Что ж, буфер (он же массив символов) просто вычисляется оффсетом.
А как же обстоит дело с самим System.String?
Открываем файл %папка с архивом%\sscli20\clr\src\bcl\system\string.cs
Видим следующие строки:
Однако System.String в своей работе полагается на COMString, реализующим сам конструктор, а также многие методы (PadLeft и т.п.).
Чтобы правильно сопоставить названия методов из фреймворка и внутренних C++ реализаций советую посмотреть файл %папка с архивом%\sscli20\clr\src\vm\ecall.cpp
Ну и чтобы окончательно убедиться в том, что m_firstChar является указателем, рассмотрим, например, часть кода метода Join:
Немного другой вариант с подсчетом (но с такими же результатами) приводит знаменитый Jon Skeet.
Перед тем как двигаться вперед, хотелось бы вспомнить и про стек.
Стек – контейнер, создаваемый средой при каждом вызове любого метода. В нем хранятся все данные, необходимые для завершения вызова (адреса локальных переменных, параметры и т.д.).
Таким образом, вызовы дерево вызовов представляют собой FIFO-контейнер, состоящий из стеков. При завершении вызова текущего метода, стек очищается и уничтожается, возвращая управление родительской ветке.
Как я уже писал выше, для переменных ссылочных типов, в стек помещается значение фиксированного размера (4 байта, например, для x86, тип DWORD), содержащее адрес экземпляра объекта, созданного в обычной куче.
В стеке по-умолчанию размещаются экземпляры примитивных типов, не участвующих в упаковке (boxing).
Однако при некоторых оптимизациях JIT- может сразу же расположить значения переменных в регистрах процессора, минуя ОЗУ.
Вспомним, что такое регистр процессора — блок ячеек памяти, образующий сверхбыструю оперативную память внутри процессора, использующаяся самим процессором и большой частью недоступен программисту.
Чем больше кэш CPU, тем более высокую производительность Вы можете получить, независимо от программной платформы.
Справочники
Этот параметр политики определяет, какие пользователи могут создавать глобальные объекты, доступные для всех сеансов. Пользователи по-прежнему могут создавать объекты, которые являются специфическими для их собственного сеанса, если они не имеют этого права пользователя.
Глобальный объект — это объект, который создается для использования любым количеством процессов или потоков, даже тех, которые не запущены в сеансе пользователя. Служба удаленного рабочего стола использует глобальные объекты в своих процессах для облегчения подключений и доступа.
▌ Устройство GC
Как известно, управлением памятью (созданием и уничтожением объектов) занимается сборщик мусора – он же Garbage Collector (GC).
Для работы приложения CLR сразу же инициализирует два сегмента виртуального адресного пространства – Small Object Heap и Large Object Heap.
Небольшая заметка: виртуальная память представляет собой логическое представление памяти, а не физическое. Физическая память выделяется только по мере необходимости. Каждому процессу в современной операционной системе выделяется виртуальное адресное пространство максимально адресуемого размера (4GB для 32-х битных ОС) с разделением на страницы (для платформ x86, IA-64, PowerPC-64 минимальный размер составляет 4KB, SPARC – 8 KB). Благодаря этому становится возможным изолирование адресного пространства одного процесса от другого, а также появляется возможность использования подкачки на диске.
Для выделения и возвращения обратно системе памяти GC использует Win32-функции VirtualAlloc и VirtualFree.
- стек
- статичные (глобальные) объекты
- финализируемые объекты
- неуправляемые Interop-объекты (CLR-объекты, принимающие участие в COM/unmanaged вызовах)
- регистры процессора
- другие CLR-объекты со ссылками
- Generation 0. Жизненный цикл объектов этого поколения самый короткий. Обычно к Gen0 относятся временные переменные, созданные в теле методов.
- Generation 1. Жизненный цикл объектов этого поколения также короткий. К нему относятся объекты с промежуточным временем жизни – объекты, переходящие из Gen0 в Gen2.
- Generation 2. Представляет собой наиболее долгоживущие объекты. Также объекты размером более 85 000 байт автоматически попадают в Large Object Heap и помечаются как Gen2.
В LOH попадают объекты не только размером более 85 000 байт, но также некоторые типы массивов.
Так массив из System.Double при размере 10 600 элементов (85000 / 8 байт) должен попасть в LOH. Однако это происходит уже при размере 1000+.
Объекты в управляемой куче располагаются один за другим, что в случае удаления большого количества объектов может приводить к фрагментации.
Однако для решения данной проблемы CLR – всегда (за исключением ручного управления памятью) дефрагментирует Small Object Heap.
Процесс выглядит следующим образом: текущие объекты копируются в свободную память (пробелы в куче, которые автоматически исчезают).
Таким образом, достигается минимальное потребление памяти, однако это требует и определенного процессорного времени. Однако это не должно волновать, т.к. для объектов Gen0, Gen1 задержка составляет всего лишь 1 мс.
Что же насчет Large Object Heap? Она никогда не дефрагментируется (почти никогда). Это потребовало бы большое количество времени, что может сказаться плохо на работе приложения. Однако это не значит, что CLR начинает потреблять все больше и больше памяти просто так. Во время Full-GC (Gen0, Gen1, Gen2) система все же возвращает ОС память, освобождаясь от уже мертвых объектами из LOH (или дефрагментацией SOH).
Также CLR располагает новые объекты в LOH не только один за другим, как в SOH, например, но и на местах уже свободной памяти, не дожидаясь Full-GC.
Запуск GC не детерминирован, за исключением вызова метода GC.Collect().
- При достижении поколения Gen0 размера в 256 KB
- При достижении поколения Gen1 размера в 2 MB
- При достижении поколения Gen2 размера в 10 MB
Еще одним моментом, при работе с памятью является использование неуправляемых ресурсов.
Т.к. неуправляемые ресурсы могут содержать любые объекты вне зависимости от длительности жизни и GC не детерминирован, то для этих целей существует финализатор.
При запуске приложения, CLR находит типы с финализаторами и исключает их из обычной сборки мусора (но это не означает, что объекты не привязаны к поколениям).
После окончания работы GC, в отдельном потоке обрабатываются финализируемые объекты (вызов метода Finalize).
Пример реализации паттерна Dispose:
Workstation mode – GC максимально оптимизирован для работы на клиентских машинах. Старается особо не загружать процессор, а также работает с минимальными задержками для приложений с UI. Доступен в двух режимах – параллельном и синхронном.
При параллельном режиме GC запускается в отдельном потоке (с приоритетом normal) для Gen2-поколения, блокируя при этом работу эфемерных поколений (аллокации новых объектов не возможны, все потоки приостановлены).
Если приложению доступно очень (. ) много свободной памяти, то SOH не становится компактным (GC жертвует память, ради отзывчивости приложения).
Таким образом, Workstation mode – идеально подходит для GUI-приложений.
Кроме того, если необходимо использовать именно серверный GC, то включить его можно так:
Для проверки можно использовать в коде свойство GCSettings.IsServerGC.
Для принудительного отключения Workstation Concurrent GC, используем следующие параметры:
По-умолчанию, параллельный режим включен для Workstation GC. Однако, если процессор – одноядерный, то GC автоматически переходит в синхронный режим.
Рассмотрим теперь Server GC.
Server GC разделяет управляемую кучу на сегменты, количество которых равно количеству логических процессоров, используя для обработки каждого из них по одному потоку.
Небольшая заметка: логический процессор необязательно соответствует физическому процессору. Системы с несколькяими физическими процессорами (т.е. несколькими сокетами) и многоядерные процессоры предоставляют ОС множество логических процессоров, причем ядро (. ) также может представлять собой более 1 логического процессора (например, при использовании технологии Hyper-threading от Intel).
По-умолчанию, LatencyMode для Workstation Concurrent GC установлен как Interactive, Server – Batch.
Существует еще и LowLatency, но его использование может привести к OutOfMemoryException, т.к. в данном режиме GC Полная сборка мусора происходит только в случае высокой нагрузки на память. Также его нельзя включить для Server GC.
В чем же разница между Batch и Interactive?
Т.к. Server GC делит управляемую кучу на несколько сегментов (каждый их которых обслуживает отдельный логический процессор), то в параллельной сборке мусора уже нет необходимости (если бы еще один поток запускался на др. логическом процессоре). Этот режим принудительно переопределяет параметр gcConcurrent. Если включен режим gcConcurrent, режим Batch будет препятствовать дальнейшей параллельной сборке мусора (. ). Batch эквивалентен непараллельной сборке мусора на рабочей станции. При использовании такого режима характерна обработка больших (. ) объемов данных.
Следует помнить, что изменение значения GCLatencyMode влияет на текущие запущенные потоки, что означает воздействие на саму среду исполнения и неуправляемый код.
А т.к. потоки могут выполняться на различных логических процессорах, то отсутствует гарантия мгновенного перевода режима GC.
А что если другой поток, захочет изменить данное значение. А если потоков 100?
Чувствуете, что назревает проблема для многопоточного приложения? И особенно для CLR – ведь может быть вызвано исключение в самой среде, а не в коде приложения.
Именно для таких случаев и существует constrained execution region (CER) – гарантия обработки всех исключений (как синхронных, таки асинхронных).
В блоке кода, помеченного как CER, среде исполнения запрещается бросать некоторые асинхронные исключения.
Например, при вызове Thread.Abort() поток, исполняемый под CER, не будет прерван до тех пор, пока не завершится исполнение CER-защищенного кода.
Также CLR при инициализации подготавливает CER, чтобы гарантировать работу даже при нехватке памяти.
Рекомендуется не использовать CER для больших участков кода, т.к. существует ряд ограничений для такого рода кода: boxing, вызов виртуальных методов, вызов методов через reflection, использование Monitor.Enter и т.д.
Но не будем углубляться в это дело и посмотрим, как безопасно переключить режим LatencyMode.
Точно нет? И даже ничего не смутило?
Хм…неужели вопрос о невозможности аллокации новых объектов в эфемерных поколениях никак не заинтересовал?
Целью его создания было уменьшение задержек при Full-GC, в частности Gen2.
Background GC – это тот же самый Concurrent GC, за одним исключением – при Full-GC эфемерные поколения не блокируется для аллокации новых объектов.
Согласитесь, что обработка Gen2 и LOH весьма затратное дело. А блокирование Gen0, Gen1 – т.е. обычной работы приложения может вызывать задержки (при определенных ситуациях).
Еще один вопрос, который адресует новый GC – откладывание аллокации новых объектов при достижении лимита размеров управляемой кучи (16 MB – desktop, 64 – server).
Теперь для предотвращения такой ситуации действует не только background thread для Gen2, но еще и foreground thread (да-да, у нас есть еще и Foreground GC), который помечает мертвые объекты из эфемерных поколений и объединяет текущие эфемерные поколения с Gen2 (т.к. объединение менее затратная операция, чем копирование) и передает их на обработку background thread, тем самым позволяя выделять память под новые объекты (напоминаю, что в Background GC Gen0, Gen1 не блокируются во время работы GC для Gen2).
Уменьшение количества задержек можно сравнить на нижеприведенном графике:
Проблема 2. Право пользователя Create Global Objects (SeCreateGlobalPrivilege)
Право пользователя "Создание глобальных объектов" (SeCreateGlobalPrivilege) — это параметр безопасности Windows 2000, который впервые появился в Windows 2000 с пакетом обновления 4 (SP4). Для создания глобальных объектов в сеансе служб терминалов учетной записи пользователя требуется право пользователя. Обратите внимание, что пользователи по-прежнему могут создавать объекты, относящиеся к сеансу, без назначения этого права пользователя. По умолчанию участникам группы "Администраторы", системной учетной записи и служб, запущенным диспетчером управления службами, назначается право пользователя "Создание глобальных объектов".
Устранение неполадок для проблемы 2
Некоторые программы могут работать неправильно после установки Windows 2000 с пакетом обновления 4 (SP4).
После установки Windows 2000 с пакетом обновления 4 (SP4) на компьютере некоторые программы могут работать неправильно. Эта проблема может возникать в ситуациях, когда учетная запись пользователя, используемая для запуска программы, не имеет права пользователя "Создание глобальных объектов".
Чтобы устранить эту проблему, определите учетную запись пользователя, используемую для запуска программы, а затем назначьте этому пользователю право "Создание глобальных объектов". Для этого выполните следующие действия:
Чтобы устранить неполадки, когда не удается определить учетную запись пользователя, используемую для запуска программы, и проверить, вызваны ли симптомы, вызванными правом пользователя, назначьте пользователю право "Создать глобальные объекты" группе "Все", а затем запустите программу. Если программа работает правильно, проблема, с которой вы столкнулись, может быть вызвана новым параметром безопасности.
Компьютер перестает отвечать (зависает) при перезапуске сервера Windows 2000 после установки mcAfee Parental Control.
Описывает лучшие практики, расположение, значения, управление политикой и соображения безопасности для параметра Политики безопасности для создания глобальных объектов.
Уязвимость
Для создания глобальных объектов в сеансах удаленного рабочего стола для учетной записи пользователя требуется право пользователя создать глобальные объекты. Пользователи по-прежнему могут создавать объекты session-specfic, не написав это право пользователя. Назначение этого права может быть угрозой безопасности.
По умолчанию членам группы Администраторы, учетной записи Системы и службам, которые запущены диспетчером управления службами, назначено право пользователя Создать глобальные объекты. Пользователи, добавленные в группу удаленных пользователей настольных компьютеров, также имеют это право пользователя.
Проблема 1. Право пользователя Impersonate a Client AfterAuthentication (SeImpersonatePrivilege)
Право пользователя Impersonate a client after authentication (SeImpersonatePrivilege) — это параметр безопасности Windows 2000, который впервые появился в Windows 2000 с пакетом обновления 4 (SP4). По умолчанию членам локальной группы администраторов устройства и учетной записи локальной службы устройства назначается право "Олицетворение клиента после проверки подлинности". Следующие компоненты также имеют это право пользователя:
- Службы, запущенные диспетчером управления службами
- Серверы модели COM, запущенные с помощью com-инфраструктуры и настроенные для работы под определенной учетной записью
При назначении пользователю права "Олицетворение клиента после проверки подлинности" вы разрешаете программам, которые выполняются от имени этого пользователя, олицетворять клиент. Этот параметр безопасности помогает предотвратить олицетворение неавторизованными серверами клиентов, подключаемых к ним с помощью таких методов, как вызовы удаленных процедур (RPC) или именованные каналы. Дополнительные сведения о функции SeImpersonatePrivilege см. на следующем веб-сайте Майкрософт:
Имитация клиента после проверки подлинности
Дополнительные сведения о функциях Олицетворения (например, ImpersonateClient, ImpersonateLoggedOnUser и ImpersonateNamedPipeClient) см. в документации по пакету SDK платформы Майкрософт. Чтобы просмотреть эту документацию, посетите следующий веб-сайт Майкрософт:
Имитация клиента после проверки подлинности
Устранение неполадок для проблемы 1
Некоторые программы, использующие олицетворение, могут работать неправильно после установки Windows 2000 с пакетом обновления 4 (SP4).
После установки Windows 2000 с пакетом обновления 4 (SP4) на компьютере некоторые программы, использующие олицетворение, могут работать неправильно.
Эта проблема может возникать в ситуациях, когда учетная запись пользователя, используемая для запуска программы, не имеет права пользователя "Олицетворение клиента после проверки подлинности".
На компьютерах под управлением Windows 2000 с пакетом обновления 3 (SP3) и более ранних версий для олицетворения клиента не требуется право пользователя. Поэтому некоторые программы, использующие олицетворение, могут работать неправильно после установки Windows 2000 с пакетом обновления 4 (SP4).
Чтобы устранить эту проблему, определите учетную запись пользователя, используемую для запуска программы, а затем назначьте этому пользователю право "Олицетворение клиента после проверки подлинности". Для этого выполните следующие действия:
Чтобы устранить неполадки, когда не удается определить учетную запись пользователя, используемую для запуска программы, и проверить, вызваны ли симптомы, вызванными правом пользователя, назначьте пользователю "Олицетворение клиента после проверки подлинности" право на группу "Все", а затем запустите программу. Если программа работает правильно, проблема, с которой вы столкнулись, может быть вызвана новым параметром безопасности.
Возможные значения
- Определяемый пользователей список учетных записей
- Учетные записи по умолчанию, перечисленные ниже
Противодействие
Если не администраторам необходимо получить доступ к серверу **** с помощью удаленного рабочего стола, добавьте пользователей в группу удаленных пользователей настольных компьютеров, а не устройте им это право пользователя.
-->
Описание события:
Это событие создается при внесении одного из следующих изменений в местную политику безопасности компьютера:
Изменены параметры политики блокировки компьютера \Security Параметры\Account Policies\Account Lockout Policy.
Были изменены параметры компьютерной политики \Security Параметры\Account Policies\Password Policy.
Параметр групповой политики "Безопасность сети: принудительное журналов при истечении часов действия логотипа" был изменен.
Был изменен функциональный уровень домена или изменены некоторые другие атрибуты (см. сведения в описании событий).
Примечание. Рекомендации приведены в разделе Рекомендации по мониторингу безопасности для этого события.
XML события:
Необходимые роли сервера: нет.
Минимальная версия ОС: Windows Server 2008, Windows Vista.
Версии события: 0.
Описания полей:
Тип изменения [Type = UnicodeString]: тип изменения, которое было сделано. Формат "политика_name изменен". Вот некоторые возможные значения политики_name:
Значение | Имя групповой политики \ Описание |
---|---|
Политика блокировки | Изменены параметры политики блокировки компьютера \Security Параметры\Account Policies\Account Lockout Policy. |
Политика паролей | Были изменены параметры компьютерной политики \Security Параметры\Account Policies\Password Policy. |
Политика входа | "Безопасность сети: принудительное журналов, когда срок действия логотипаистекает " параметр групповой политики был изменен. |
- | Был изменен атрибут доменной учетной записимашины (ms-DS-MachineAccountQuota). |
Тема:
- Security ID [Type = SID]: SID учетной записи, внося изменения в определенную локализованную политику. Средство просмотра событий автоматически пытается разрешить идентификатор безопасности SID и отобразить имя учетной записи. Если идентификатор безопасности разрешить не удается, в событии будут отображены исходные данные.
Примечание. . Идентификатор безопасности (SID) представляет собой строковое значение переменной длины, которое используется для идентификации доверенного лица (субъекта безопасности). Каждая учетная запись имеет уникальный идентификатор безопасности, выданный центром сертификации, таким как контроллер домена Active Directory, который хранится в базе данных безопасности. Каждый раз, когда пользователь входит в систему, система получает идентификатор безопасности этого пользователя из базы данных и помещает ее в маркер доступа этого пользователя. Система использует идентификатор безопасности в маркере доступа для идентификации пользователя во всех последующих операциях с Безопасностью Windows. Если идентификатор SID использовался как уникальный идентификатор для пользователя или группы, он не может использоваться повторно для идентификации другого пользователя или группы. Дополнительные сведения о SID см. в разделе Идентификаторы безопасности.
Имя учетной записи [Type = UnicodeString]— имя учетной записи, в результате которого была выполнена смена определенной локальной политики.
Account Domain [Type = UnicodeString]: домен субъекта или имя компьютера. Форматы различаются и включают в себя следующее:
Пример имени домена NETBIOS: CONTOSO
Полное имя домена в нижнем регистре: contoso.local
Полное имя домена в верхнем регистре: CONTOSO.LOCAL
Для некоторых известных субъектов безопасности, таких как LOCAL SERVICE или ANONYMOUS LOGON, значение этого поля равно "NT AUTHORITY".
Для учетных записей локальных пользователей это поле будет содержать имя компьютера или устройства, к которым принадлежит эта учетная запись, например: "Win81".
Logon ID [Type = HexInt64]: шестнадцатеричное значение, которое может помочь сопоставить это событие с недавними событиями содержащими тот же идентификатор входа, например: “4624: Учетная запись успешно вошла в систему.”
- Доменное имя [Type = UnicodeString]: имя домена, для которого были внесены изменения политики.
- Domain ID [Type = SID]: SID домена, для которого были внесены изменения политики. Средство просмотра событий автоматически пытается разрешить идентификатор безопасности SID и отобразить имя учетной записи. Если идентификатор безопасности разрешить не удается, в событии будут отображены исходные данные.
Измененные атрибуты: Для атрибутов, которые не были изменены, значение будет - ".
- Min. Возраст пароля [Type = UnicodeString]: "\Security Параметры\Account Policies\Password Policy\Minimum password age" group policy. Числовая величина.
Max. Возраст пароля [Type = UnicodeString]: "\Security Параметры\Account Policies\Password Policy\Maximum password age" group policy. Числовая величина.
Force Logoff [Type = UnicodeString]: "\Security Параметры\Local Policies\Security Options\Network security: Force logoff when logon hours expire" group policy.
Порог блокировки [Type = UnicodeString]: "\Security Параметры\Account Policies\Account Lockout Policy\\Account lockout threshold" group policy. Числовая величина.
**** Окно наблюдения блокировки [Type = UnicodeString]: "\Security Параметры\Account Policies\Account Lockout Policy\\Reset account lockout counter after" group policy. Числовая величина.
Длительность блокировки [Type = UnicodeString]: "\Security Параметры\Account Policies\Account Lockout Policy\\Account lockout duration" group policy. Числовая величина.
Свойства пароля [Type = UnicodeString]:
Min. Длина пароля [Type = UnicodeString]: "\Security Параметры\Account Policies\Password Policy\Minimum password length" group policy. Числовая величина.
Длина истории паролей [Type = UnicodeString]: "\Security Параметры\Account Policies\Password Policy\Enforce password history" group policy. Числовая величина.
Квота учетной записи машины [Type = UnicodeString]: изменен атрибут домена ms-DS-MachineAccountQuota. Числовая величина.
Смешанный режим домена [Type = UnicodeString]: в этом документе нет сведений об этом поле.
Версия поведения домена [Type = UnicodeString]: атрибут домена msDS-Behavior-Version был изменен. Числовая величина. Возможные значения:
Глобальные объекты получили широкое распространение из-за удобства их использования. В них хранят настройки, игровые сущности и вообще любые данные, которые могут понадобиться где угодно в коде. Передача же в функцию всех нужных аргументов может раздуть список параметров до очень большого размера. Помимо удобства есть и недостатки: порядок инициализации и разрушения, дополнительные зависимости, сложность написания юнит-тестов. Многие программисты предвзято считают, что глобальные переменные используют только новички и это уровень студенческих лабораторных. Однако в больших проектах, как CryEngine, UDK, OGRE, глобальные объекты также применяются. Разница только в уровне владения этим инструментом.
Итак, что же за зверь этот глобальный объект, как его приручить и пользоваться удобствами, сведя недостатки к минимуму? Давайте разбираться вместе.
Существует масса способов создать глобальный объект. Самый простой — объявить extern-переменную в заголовочном файле и создать её экземпляр в cpp:
Более абстрактным подходом является шаблон одиночка (singleton).
Чем хорошо данное решение, что ему уделяется так много внимания? Оно позволяет использовать объект в любом месте программы. Весьма удобно, и соблазн сделать так очень велик. Проблемы начинаются, когда нужно заменить часть системы, не нарушив работу всего остального, или же протестировать код. В последнем случае нам придётся инициализировать чуть ли не все глобальные переменные, которые использует интересующий нас метод. Более того, вышеперечисленные трудности очень усложняют замену поведения объекта на желаемое для тестов. Также нет контроля за порядком создания и удаления, что может привести к неопределённому поведению или падениям программы. Например, когда обращаются к ещё не созданному или уже удалённому глобальному объекту.
В общем случае предпочтительно использование локальных переменных вместо глобальных. К примеру, если вам нужно отрисовать некий объект и есть глобальный Renderer, то лучше его передать напрямую в метод void Draw(Renderer& render_instance) , а не использовать глобальный Render::Instance (). Больше примеров и обоснований, почему не стоит использовать синглтон, можно почитать в посте.
Однако совсем без глобальных объектов обойтись сложно. Если нужен доступ к настройкам или прототипам, то к каждому объекту не прицепишь все нужные контейнеры, фабрики и прочие параметры. Этот случай мы и будем рассматривать.
Для начала постановка задачи:
- К объекту должен быть доступ из любой части программы.
- Все перерабатываемые глобальные объекты должны храниться централизованно — для простоты поддержки.
- Возможность добавлять и/или заменять глобальные объекты в зависимости от контекста — реальный запуск или тестирование.
Интересное решение было подсмотрено в недрах CryEngine (смотреть SSystemGlobalEnvironment ). Глобальные объекты завёрнуты в одну структуру и являются указателями на абстрактные сущности, которые инициализируются в нужный момент в нужном месте программы. Никаких дополнительных накладных расходов, никаких лишних надстроек, контроль за типом во время компиляции – красота!
CryEngine представляет собой достаточно старый и годами обточенный проект, где все интерфейсы устаканились, а новое прикручивается подобно тому, что существует на данный момент. Поэтому нет необходимости придумывать дополнительные обёртки или способы работы с глобальными объектами. Есть и другой вариант — молодой и бурно развивающийся проект, где нет строгих интерфейсов, где функционал постоянно меняется, что сподвигает вносить правки в интерфейсы достаточно часто. Хочется иметь решение, которое поможет в старых проектах производить рефакторинг, а в новых, где всё же необходим глобальный доступ, минимизировать недостатки использования. Для поиска ответа можно попробовать подняться на уровень выше и посмотреть на проблему под другим углом – создать хранилище глобальных объектов, наследуемых от GlobalObjectBase . Использование оболочки добавит операции во время исполнения, поэтому обязательно нужно обратить внимание на производительность после изменений.
Для начала необходимо создать базовый класс, наследников которого можно будет поместить в объект-хранилище.
Теперь само хранилище. Для доступа из любой части программы объект этого класса необходимо сделать глобальным при помощи одного из стандартных способов, который вам понравится больше.
Для работы с данным видом объектов достаточно их типа, поэтому интерфейс GlobalObjectsStorage составляют шаблонные методы, которые передают нужные данные реализации.
Итак, первый тест-драйв – работает!
Но это ещё не всё – подменять объекты для разных контекстов нельзя. Исправляем, добавив класс-родитель для хранилища, перенеся шаблонные методы туда, и сделав виртуальными методы имплементации.
Часто над глобальными объектами нужно проводить разные манипуляции во время создания или удаления. В наших проектах это чтение данных с диска (например, файла настроек для подсистемы), обновление данных игрока, которое происходит при загрузке приложения и через определённый интервал времени во время игры, и обновление внутриигрового цикла. У других программ могут быть дополнительные или совершенно иные действия. Поэтому конечный базовый тип будет определяться пользователем класса и позволит избежать множественного вызова одинаковых методов.
Вопросы безопасности
В этом разделе описывается, каким образом злоумышленник может использовать компонент или его конфигурацию, как реализовать меры противодействия, а также рассматриваются возможные отрицательные последствия их реализации.
▌ Ручное управление памятью
Но также мы можем работать с неуправляемым кодом.
Для GCHandle CLR использует отдельную таблицу для каждого AppDomain.
GCHandle уничтожаются при вызове GCHandle.Free(), либо при выгрузке AppDomain.
- Normal
- Weak
- Weak Track Resurrection
- Pinned
Когда может понадобиться ручная работа с памятью, спросите Вы?
Например, для копирования массива байтов.
Помните, что GC дефрагментирует SOH? При некоторых ситуациях фиксация объектов в куче может привести к неэффективной дефрагментации SOH.
- новую CLR
- новый GC
- новую систему обработки системных исключений
- новый Thread Pool
- новая модель безопасности
Для использования старой модели безопасности:
Обработка системных SEH-исключений изменилась, но для обратной совместимости вносим следующие строчки в конфигурационный файл:
Если же Ваше приложение использует также и нативный код, то:
Групповая политика
Параметры применяются в следующем порядке с помощью объекта групповой политики (GPO), который перезаписывал параметры на локальном компьютере при следующем обновлении групповой политики:
- Параметры локальной политики
- Параметры политики сайта
- Параметры политики домена
- Параметры политики подразделения
Когда локальный параметр серый, он указывает, что GPO в настоящее время контролирует этот параметр.
Аннотация
В этой статье рассматриваются права пользователя "Олицетворение клиента после проверки подлинности" и "Создание глобальных объектов". Эти новые параметры безопасности впервые появились в Windows 2000 с пакетом обновления 4 (SP4) и помогают повысить безопасность в Windows 2000. В этой статье описываются новые параметры безопасности, а также содержатся сведения о некоторых известных проблемах, которые могут возникнуть, и способах их устранения.
Учитывайте следующие проблемы при применении прав пользователя "Олицетворение клиента после проверки подлинности" и "Создание глобальных объектов" с помощью политики домена по умолчанию или групповая политика:
Права пользователя "Олицетворение клиента после проверки подлинности" и "Создание глобальных объектов" применяются только к компьютерам, работающим под управлением Windows 2000 с пакетом обновления 4 (SP4) или более поздней версии.
Вы можете использовать политику домена по умолчанию или групповая политика для применения параметров безопасности "Олицетворение клиента после проверки подлинности" и "Создание глобальных объектов" к компьютерам в вашей среде, если компьютеры работают под управлением Windows 2000 с пакетом обновления 2 (SP2) или более поздней версии. Обратите внимание, что хотя параметры безопасности можно развернуть в среде, которая содержит компьютеры на основе Windows 2000 с пакетом обновления 2 (SP2) и Windows 2000 с пакетом обновления 3 (SP3), эти параметры безопасности применяются только к компьютерам на базе Windows 2000 с пакетом обновления 4 (SP4). Эти параметры не применяются к компьютерам под управлением Windows 2000 с пакетом обновления 2 (SP2) или Windows 2000 с пакетом обновления 3 (SP3).
Не используйте политику домена по умолчанию или другой групповая политика для применения этих новых прав пользователя к компьютерам под управлением Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1). Обратите внимание, что если вы используете политику домена по умолчанию или другой групповая политика для применения этих прав пользователей к компьютерам под управлением Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1), распространение параметров безопасности политики завершится ошибкой. То есть политика не распространяется на компьютеры Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1), а права пользователей не отображаются в оснастке local Security Параметры. Следующие сценарии могут возникнуть, если вы используете политику домена по умолчанию или другой групповая политика для применения этих новых прав пользователя к компьютерам под управлением Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1):
Дополнительные параметры политики безопасности, расположенные в том же объекте групповая политика и предназначенные для устройств Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1), не распространяются на конечные устройства.
Дополнительные параметры политики безопасности, применяемые с помощью других групповых политик по пути SDOU (сайт, домен, подразделение) к устройствам Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1), которые будут распространяться.
Если один или оба этих новых параметра безопасности предназначены для устройств Windows 2000 или Windows 2000 с пакетом обновления 1 (SP1), локальная оснастка безопасности MMC на этих устройствах не может правильно отображать какие-либо параметры безопасности. Однако все параметры безопасности, применяемые к целевым устройствам из других доменных объектов групповая политика (которые не содержат новых параметров), будут по-прежнему применяться к этим целевым устройствам.
Аналогичным образом можно использовать политику безопасности контроллера домена по умолчанию для применения параметров безопасности "Олицетворение клиента после проверки подлинности" и "Создание глобальных объектов" к контроллерам домена в вашей среде, если контроллеры домена работают Windows 2000 с пакетом обновления 2 (SP2) или более поздней версии. Обратите внимание, что хотя параметры безопасности можно развернуть в среде, которая содержит контроллеры домена на основе Windows 2000 с пакетом обновления 2 (SP2) и Windows 2000 с пакетом обновления 3 (SP3), эти параметры безопасности применяются только к контроллерам домена на основе Windows 2000 с пакетом обновления 4 (SP4). Эти параметры не применяются к контроллерам домена, работающим под управлением Windows 2000 с пакетом обновления 2 (SP2) Windows 2000 с пакетом обновления 3 (SP3).
▌ Регистры процессора || FastCall
Первой оптимизацией, которую проводит JIT – оптимизация, присущая и компилятору VC++, а именно – FastCall.
Суть метода заключается в том, что в регистры ECX, EDX записываются первые 2 параметра метода.
При платформе x64 – используются регистры RCX, RDX, R8, R9.
Что же это дает?
Например, при реализации какого-либо алгоритма наиболее часто используемый параметр следует выставить первым (пусть это и микрооптимизация, но все же).
Так для вышеприведенного кода переменные startIndex и endIndex будут находиться именно в регистрах ECX, EDX, остальные (x, y) в стеке.
Проверим – для этого запустим дебаггер.
Для того чтобы посмотреть в текущие регистры наберем команду CTRL + D, R .
В этой статье рассматривается олицетворение клиента после проверки подлинности и создание глобальных объектов с правами пользователя.
Применяется к: Windows Server 2012 R2
Исходный номер базы знаний: 821546
Значения по умолчанию
По умолчанию члены группы администраторов имеют это право, как и учетные записи локальной службы и сетевой службы в поддерживаемых версиях Windows. Служба включена для обратной совместимости с более ранними версиями Windows.
В следующей таблице приведены фактические и действующие значения по умолчанию для этой политики. Значения по умолчанию также можно найти на странице свойств политики.
Тип сервера или объект групповой политики | Значение по умолчанию |
---|---|
Default Domain Policy | Не определено |
Политика контроллера домена по умолчанию | Администраторы Локализованная служба Служба сети Обслуживание |
Параметры по умолчанию для автономного сервера | Администраторы Локализованная служба Служба сети Обслуживание |
Действующие параметры по умолчанию для контроллера домена | Администраторы Локализованная служба Служба сети Обслуживание |
Действующие параметры по умолчанию для рядового сервера | Администраторы Локализованная служба Служба сети Обслуживание |
Действующие параметры по умолчанию для клиентского компьютера | Администраторы Локализованная служба Служба сети Обслуживание |
Всё ли в итоге у нас хорошо?
Понятно, что производительность от подобной обёртки будет хуже, чем от использования глобального объекта напрямую. Для теста было создано десять различных типов. Сначала они использовались как глобальный объект без наших изменений, затем через DefaultObjectsStorage . Результат для 1 000 000 вызовов.
Текущий код работает медленнее обычного глобального объекта почти в 18 раз! Профайлер подсказывает, что больше всего времени занимает typeid(*obj).hash_code() . Раз добыча данных о типах во время исполнения тратит очень много процессорного времени, то нужно её обойти. Самый простой способ сделать это — хранить хеш типа в базовом классе глобальных объектов ( GlobalObjectBase ).
Также стоит поменять метод ObjectStorageBase::AddGlobalObject и DefaultObjectsStorage:: GetGlobalObjectImpl . Дополнительно статически сохраняем данные о типе в шаблонной функции родительского класса ObjectStorageBase::GetGlobalObject .
Вышеуказанные изменения позволяют существенно уменьшить время поиска нужного объекта, и отличие будет уже не в 18 раз, а в 1,25 — это вполне приемлемо в большинстве случаев.
Кроме того, чтобы не менять целое хранилище для тестов, можно переопределять метод GlobalObjectBase::RecalcHashCode и выборочно заменять только нужные объекты. Для замены в основном классе необходимо сделать виртуальными нужные для теста методы и тестовый класс-наследник.
Первопроходцем для внедрения этого подхода был Fishdom, где несколько объектов стали использоваться через данную обёртку. Это позволило убрать зависимости, покрыть часть кода тестами и сделать удобнее монотонную работу по вызову методов (Init, Release, Update) в нужных местах.
Читайте также: