1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

Клиент Lineage2 - утечка объектов GDI

Discussion in 'Архивы' started by Radix, Mar 17, 2012.

Thread Status:
Not open for further replies.
  1. Radix

    Radix Innova Group

    Joined:
    10.02.10
    Messages:
    6,061
    Likes Received:
    224
    доброго времени суток.

    сегодня попробуем вместе рассмотреть стародавнюю проблему игрового клиента lineage2, связанную с утечкой объектов gdi (http://ru.wikipedia.org/wiki/gdi - сразу прошу прощения у профи за wiki, ибо там информация мягко говоря любительского характера, но мне сказали, что "wiki" сейчас модно, и без ссылки на нее ну никак). для незнакомых с вопросом и / или не слишком усердных в чтении документации microsoft, пожалуй, поясню, чем в принципе нам грозит данная проблема с утечкой.

    если говорить об этом совсем просто, то любой подобный объект ос (неважно просто это описатель - handle, или user-object, или же, как у нас с вами, gdi-object) будет занимать некоторое количество памяти. разумеется, количество доступной памяти у нас все же ограничено, но, как показывает практика, дело далеко не в этом, учитывая современные вычислительные мощности ваших pc. куда более строгие рамки в использовании тех или иных объектов на нас накладывает архитектура самой операционной системы windows. причем среди ее внутренних ограничений есть "рамки" нескольких типов. к примеру, для каждого отдельного процесса разрешается создавать и поддерживать по умолчанию не более 10000 gdi-объектов (исключение читаем в http://msdn.microsoft.com/en-us/library/windows/desktop/ms724291(v=vs.85).aspx и http://weblogs.asp.net/mikedopp/archive/2008/05/16/increasing-user-handle-and-gdi-handle-limits.aspx), а всего в силу использования 16-разрядных описателей под объекты - под нужды всех процессов системы могут быть использованы не более 2^16 = 65536 объектов gdi. ну а уж если закопаться еще глубже и вспомнить, что под регистрацию и поддержку gdi-объектов системой будут использованы общий выгружаемый пул / пул сессии, то при создании объектов определенного вида, мы можем упереться не в физ. лимиты по количеству объектов, а в лимиты самого пула (т.е. исчерпать всю память, отведенную ядром ос под данный пул). и кстати, они не такие уж и большие - к примеру, для w2k3 - пул сессии составляет порядка 32мб, на xp лимит немного выше.

    ну да не суть что там такого сложного в windows internals (не морочу далее голову цифрами и выкладками, которые давным давно сделаны такими людьми как mark russinovich - http://blogs.technet.com/b/markrussinovich/archive/2010/03/31/3322423.aspx) - с наличием некоторых лимитов мы разобрались, и понятное дело, при стремлении общего количества наших объектов к этой границе, с системой или же нашим игровым процессом начнет творится нечто не совсем приятное. и если мы, к примеру, исчерпаем лимит по кол-ву объектов на процесс игры (т.е. подойдем в границе 10000 gdi-objects), то при следующей подобной операции игру ждет неизбежный fail - операция не будет выполнена системой, произойдет ошибка, которая будет фиксирована движком unreal с выдачей некоторого crash-скрина. представим себе и иной сценарий - если мы являемся любителем большого кол-ва окон, мы можем запросто исчерпать не просто 10000 объектов на процесс, а все 65536 объектов системы. помимо рандомно "падающих" игровых окон мы получим и интересные эффекты с экраном рабочего стола и его элементами управления:


    так или иначе, ситуация не их приятных - игра "крашится", раб. стол превращается в нечто ужасное, в общем - нормально поиграть не получится. все это действительно нас должно толкать и мотивировать на то, чтобы наконец разобраться с тем, как и из-за чего происходит утечка и перерасход объектов gdi в клиенте lineage2. справедливости ради, стоит отметить, что с эффектом знакомы не так много пользователей, о проблеме как таковой сообщали в основном именно любители запускать большее кол-во окон, в т.ч. с использованием 3-d party программного обеспечения. тут же, разумеется, в очередь по интересам встанут и любители использовать всяческого рода "ботов", "кликеров" и прочих средств для манипуляции игровым окном / движком игры с целью эмуляции присутствия игрока. в любом из описанных случаев (легальный / нелегальный) - идет достаточно большое кол-во операций с игровым окном, и как результат происходит перерасход такого ценного ресурса, как gdi-объекты.

    попробуем найти отправную точку для наших исследований, и для этого в первую очередь обратимся к тому, какая информация есть у нас о данном баге, т.е. что успели заметить, что передали лично мне например. когда мне рассказывали о данной ошибке, мне дали весьма точную формулировку - "при активации и деактивации игрового окна lineage2 появляются 2 новых хендла gdi - и так каждый раз, каждое переключение состояния окна". что ж, вполне ситуация понятна, и повторить не составит труда:

    • запустим игровой клиент lineage2
    • зафиксируем кол-во объектов gdi
    • активируем / дективируем игровое окно
    • зафиксируем кол-во объектов gdi снова

    скриншоты этого процесса и некоторые пояснения ниже:
    кол-во объектов gdi на странице принятия пс
    http://*********/37073276/image.png

    кол-во объектов gdi после активации / деактивации окна игры
    http://*********/37073275/image.png

    как видно из предложенных скриншотов, предложенный нам сценарий действительно "рабочий", ошибку мы повторили. именно +2 объекта при переключении окна. если побаловаться с окном подольше, то легко "нащелкивается" и большие значения данного типа объектов. многие тут сразу же резонно заметят - всего ~100 объектов при старте, ну и +2 при каждом переключении - это ведь так далеко от заявленных 10000 по умолчанию для windows. спорить не буду - многих проблема действительно обходит стороной, и они никогда не превысят ни один из рассмотренных нами ранее лимитов, и не получат негативного эффекта от этого. однако, даже в этом случае - просто попробуйте для себя поспорить с теми, кто с ошибкой имел дело (много окон, интенсивное переключение, много операций с окном) - они откроют для вас иную сторону вопроса, куда более жесткую и неприятную. да и, согласитесь, позволять оставлять мусор в системе из-за достаточно грубой ошибки программиста граф. движка - признак плохого тона.

    на этой ноте мы продолжаем рассмотрение вопроса с теми, кто с высказанным мнением о необходимости устранения такого недостатка игрового клиента солидарен (будь то из-за неудобства в игре, будь то по этическим соображениям чистоты кода и т.п.). и так мы теперь наглядно представляем, что сам факт утечки в наличии, и далее попробуем уточнить, что же происходит с клиентом игры, дабы разработать более четкую стратегию поиска фрагмента кода, который написан программистом мягко говоря не слишком аккуратно. что пришло в голову мне - раз мы имеем головную боль с gdi-объектом, то мы всегда можем попробовать выяснить, а что же за объект это был. как это сделать - просто сравнить список объектов до переключения игрового окна и после. согласитесь - сравнить список из 100 объектов даже визуально по удобной табличке сможет каждый из нас. поэтому - получим данные отчеты и проведем сравнение в разрезе типов объектов gdi:

    • запустим игровой клиент lineage2
    • зафиксируем таблицу gdi-объектов в разрезе типов
    • активируем / дективируем игровое окно
    • зафиксируем таблицу gdi-объектов в разрезе типов повторно
    • сравним полученные таблицы визуально или автоматизированным путем

    скриншоты этого процесса и некоторые пояснения ниже:
    gdi-объекты по типам на экране принятия пс:
    http://*********/37073818/image.png

    gdi-объекты по типам после активации / деактивации окна игры:
    http://*********/37073824/image.png

    все объекты и их сравнение тут не привожу - каждый волен сам выбрать для себя методику, поэтому представил всего лишь результат фильтрации. итого мы имеем что отличие списков до операций с окном и после состоит в 2 dc (типа hdc в windows api) объектах, т.е. имеем дело с утечкой объекта device context (http://msdn.microsoft.com/en-us/library/dd183553(v=vs.85).aspx). более того, приятным дополнением к этому будет то, что у нас на руках есть еще и хендлы (описатели) этих новеньких gdi-объектов:


    0x020129cc
    0xf80127c0


    что ж - очень хорошо. попробуем ка теперь вспомнить, с помощью каких средств ос мы можем создать gdi-объект типа devicecontext, и с помощью каких средств - уничтожить / освободить этот объект. разбалансировка в использовании этих функций (т.е. операций создания объекта должно быть больше операций освобождения ровно на 2 единицы за переключение) и даст нам указание на точный момент, когда игра начинает "разбрасываться" ресурсами системы. для этого обратимся к оф. документации microsoft на msdn:

    http://msdn.microsoft.com/en-us/library/dd183554(v=vs.85).aspx

    для себя лично я выделил следующие функции, под которые я буду настраивать in-game мониторинг:

    модуль: gdi32.dll:
    api:
    createcompatibledc
    createdca
    createdcw
    createica
    createicw
    deletedc


    модуль: user32.dll
    api:
    getdc
    getdcex
    releasedc


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

    определившись с набором api, которые поставим на мониторинг, сделаем трейс функций при однократной операции активации / деактивации игрового окна, посмотрим за балансом операций создания / удаления объектов, а также сравним полученные в результате работы граф. движка хендлы с теми, которые мы зафиксировали ранее (вспоминаем те два новых объекта и их описатели):

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

    скриншоты этого процесса и некоторые пояснения ниже:
    трейс вызовов интересующих нас функций, их параметров, результатов при однократной активации / деактивации игрового окна:
    http://*********/37074274/image.png

    стек первого несбалансированного вызова по созданию / удалению объекта gdi:
    http://*********/37074301/image.png

    стек второго несбалансированного вызова по созданию / удалению объекта gdi:
    http://*********/37074308/image.png

    как видим, из первого скриншота - вызовов поймали мы не столь много (а вообще графика дает целый каскад вызовов), а это значит с подбором фильтра мы не ошиблись и лишней работы по чтению и разбора лога мы делать не будем. итого имеем - вызовы getdcex (http://msdn.microsoft.com/en-us/library/dd144873(v=vs.85).aspx) из недр модуля uxtheme.dll (поддержка тем windows) и getdc (http://msdn.microsoft.com/en-us/library/dd144871(v=vs.85).aspx) из модуля d3ddrv.dll (а вот это уже компонент граф. движка нашей lineage2 - лежит он в папке system клиента). анализируя далее вызовы, мы видим, что для функции getdcex были вызваны несколько раз функции-антипод releasedc (http://msdn.microsoft.com/en-us/library/dd162920(v=vs.85).aspx), в результате чего объекты:


    0xe8011896
    0xab0129b7


    были сначала созданы, и затем благополучно освобождены функцией releasedc - об этом говорит возвращенное последней значение "1", что в соответствии с документацией microsoft говорит об успешности выполнения операции по освобождению объекта gdi. да и сам факт того, что это системный модуль windows, наводит нас на мысль, что ошибка то вряд ли в нем.

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


    0x020129cc
    0xf80127c0


    мы как раз заметили ранее в списке новых объектов gdi, которые "прибавились" к нашему клиенту в ходе переключения игрового окна. далее в ингейм-процессе они не участвовали и не уничтожались, а значит эти два объекта (и подобные им) и есть выражение нашей утечки. и соответственно код ведущий из d3ddrv.dll к getdc и есть сбойный фрагмент кода, приводящий к самой утечке. постараемся теперь по полученному трейсу отследить и точно найти фрагмент, в котором данное безобразие и происходит - для этого я выше привел скриншоты 2 и 3 со стеками несбалансированных вызовов getdc.

    дабы нам удобнее понаблюдать за логикой кода этого фрагмента (цепочка windrv - d3ddrv - getdc) мы узнаем базовый адрес загрузки модуля d3ddrv.dll в процесс l2.exe и загрузим его для анализа в ida по этому же адресу (можно загружать и по базе из image header, но тогда придется пересчитывать смещения, поэтому для наглядности сделаем чуть проще свою задачу):

    база загрузки модуля в клиент: 0x16780000
    http://*********/37074744/image.png

    в соответствии со стеком вызовов функция getdc была вызвана из модуля d3ddrv.dll по адресу 0x167b6891. перейдем на этот участок кода в ida и посмотрим, что в нем:
    искомый фрагмент кода, приводящий к утечки gdi-объектов типа devicecontext
    http://*********/37074952/image.png

    на скриншоте выше очень хорошо виден алгоритм для настройки гамма-коррекции изображения для раб. стола в целом (а значит и для игры в т.ч.). сначала мы "находим" рабочий стол, далее получаем для него контекст под "рисование", и после настраиваем массивы gamma ramp для передачи в функцию setdevicegammaramp (http://msdn.microsoft.com/en-us/library/dd372194(v=vs.85).aspx), которая и проведет операции по гамма-коррекции изображения (естественно при условии, что данный функционал поддерживается вашей вк).

    если рассмотреть второй несбалансированный вызов getdc по адресу 0x167b418d, то увидим вполне похожий код:
    код фрагмента второго несбалансированного вызова getdc
    http://*********/37075087/image.png

    и в первом и во втором случаях мы не найдем внутри предложенного фрагмента вызова функции-антипода releasedc, которая бы освободила полученный нами ранее контекст устройства. это же подтверждается и полученным нами ранее in-game мониторингом вызовов. т.о. при каждом переключении состояния окна мы будем иметь 2 "лишних" вызова getdc под настройку гамма-коррекции. кстати, если обратить внимание на предложенные фрагменты кода внутри d3ddrv.dll, то ситуацию можно описать еще лучше с точки зрения логичности происходящего - и первый, и второй фрагмент является экспортируемым из d3ddrv.dll. они носят имена:


    ud3drenderdevice::updategamma
    ud3drenderdevice::restoregamma


    а вызов перед этим фрагмента из windrv (т.е. откуда мы попали в d3ddrv) носит название:


    uwindowsviewport::viewportwndproc


    что говорит о том, что происходила обыкновенная обработка сообщений windows (в нашем случае интересуют как минимум 2 операции - активация окна, деактивация окна) в оконной процедуре, расположенной в модуле windrv, которая для верной отрисовки содержимого воспользовалась функционалом d3ddrv (представляющего собой некоторый мост между остальными модулями движка lineage2 и граф. функциями системы / directx), который в ответ на активацию / деактивацию должен был сначала настроить гамму при активации (ud3drenderdevice::updategamma) и затем - сбросить ее на дефолт при деактивации (ud3drenderdevice::restoregamma). собственно и получаем - 2 обработанных сообщения, две реакции модулей windrv + d3ddrv и как факт - 2 незакрытых описателя контекста устройства (devicecontext).

    вопрос на этом считаю фактически неплохо рассмотренным и полностью готовым к отправке на фикс разработчикам lineage2. надеюсь, с данным материалом фикс получится надежным (а добавить то нужно всего лишь releasedc в конце функций ud3drenderdevice::updategamma и ud3drenderdevice::restoregamma) и не слишком затратным по времени разработки / тестирования. также был бы очень рад, если данный материал, несмотря на его очевидные недостатки и ляпы, хотя бы кому-либо понравился или показался не адски скучным.

    всем приятной игры!

    ---
    tribute qa_dilemma
     
    Last edited by a moderator: Mar 17, 2012
  2. Горячий

    Горячий User

    Joined:
    13.07.10
    Messages:
    435
    Likes Received:
    9
    мать моя женщина,зачет работа:eek:k: а я то думал,что за фигня думал это что-то у меня,а вот в чем прикол был :rolleyes: окна зло :cray: надеюсь теперь корейци хоть займуться клиентом.а вы говорили radix ушел ушел
     
    Last edited by a moderator: May 29, 2015
  3. ПолосатыйНосог

    ПолосатыйНосог User

    Joined:
    03.02.10
    Messages:
    510
    Likes Received:
    16
    познавательно :)
     
  4. JustLetMePlay

    JustLetMePlay User

    Joined:
    09.08.11
    Messages:
    129
    Likes Received:
    9
    благодарствую. наконец-то вы добрались до моего пункта, описанного в бывшем треде про фрост.
     
  5. Горячий

    Горячий User

    Joined:
    13.07.10
    Messages:
    435
    Likes Received:
    9
  6. DIL

    DIL Модератор 4Game Global moderator

    Joined:
    02.03.10
    Messages:
    12,088
    Likes Received:
    1,528
    зная их фиксы, сомневаюсь, что он кардинально решит проблему клиента.
     
  7. Горячий

    Горячий User

    Joined:
    13.07.10
    Messages:
    435
    Likes Received:
    9
    но сам факт уже радует,надеюсь они на этом не оставяться
     
  8. ussyka

    ussyka User

    Joined:
    06.11.11
    Messages:
    142
    Likes Received:
    0
    я если честно вообще ни чего не понял, обьесните мне про что работа? как я понял, при запуске двух и более окон л2 система начинает какует хрень обрабатывать, в системе етой хрени лимит есть, но и при запуске много окон лимит исперпается и начнется апокалипс на компьютере... автор чтот сделал путем чего, что я не понял, кудат зашел там поменял тутпоставили, там сравнил и в итоге все отлично! что было сделано то?=) и длля чего ето?
     
  9. ВашаСовесть

    ВашаСовесть Innova Group

    Joined:
    11.02.10
    Messages:
    15,760
    Likes Received:
    921
    это техническая статья в большей степени для специалистов, хотя разжована для чайников в большей мере

    принципе она про то что корейцы тоже люди и когда пишут игры допускают ошибки...

    а в частности, что windows не любит когда запускается очень много программ которые создают очень много объектов:)
     
  10. JustLetMePlay

    JustLetMePlay User

    Joined:
    09.08.11
    Messages:
    129
    Likes Received:
    9
    самое смешное то, что этот баг присутствует как минимум с интерлюдии.
    можно зайти на любую фришку и проверить, т.е. корейцы даже не озабочены поском данной проблемы.
    за 5 лет ничего не исправить - это абсурдно.
     
  11. Ulthar

    Ulthar Moderator

    Joined:
    16.03.10
    Messages:
    5,244
    Likes Received:
    387
    раньше, он и в с4 есть точно, возможно что и в более ранних клиентах есть, но они под 7-ку увы, не работают...
     
  12. Veskkker

    Veskkker User

    Joined:
    12.05.12
    Messages:
    8
    Likes Received:
    0
    вообще ничего не понял ))) а ошибка сильно нехорошая?
     
  13. ВашаСовесть

    ВашаСовесть Innova Group

    Joined:
    11.02.10
    Messages:
    15,760
    Likes Received:
    921
    нехорошая, но все играют....


    возможно если будите делать что описанно в статье ... в течении длительного времени, возможно она проявиться
     
Thread Status:
Not open for further replies.