Тормоза при активации опции "Защита ресурсов"

Здравствуйте!

Обновился с версии 2.12 до последней версии VMProtect. Заметил одну неприятную особенность: моя программа стала работать намного медленее, причём в тех местах, где защита не применяется вовсе (например, при построении дерева оргструктуры). При тех же настройках проекта в версии 2.12 никаких тормозов нет.

Эксперементальным путём выяснил, что тормоза появляются при активации опции “Защита ресурсов”. Из руководства пользователя я не понял до конца, какие именно ресурсы имеются в виду. Если программа шифрует, например, иконки и bitmap`ы, то это может объяснить тормоза при построении графических элементов. Влияет ли эта опция на саму защиту приложения от взлома или просто прячет ресурсы от любопытных глаз?

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

Если честно, я не понимаю что именно можно оптимизировать в моём случае. Для примера я взял процедуру загрузки должностей в таблицу TcxTreeList (компонент DexExpress).

Ниже упрощённый вариант моей процедуры.

Procedure LoadPosts;
Begin
 // очищаем таблицу

 c := GetTickCount; // измеряем производительность

 PostsList.BeginUpdate;
 PostsList.Clear;

 // считывает элементы из БД
 Results := Manager.CreateCriteria<TLiteOrgTree>.AddOrder(TOrder.Asc('ElementOrder')).List;
 
  if Results.Count > 0 then
   for i := 0 to Results.Count - 1 do
    Begin
     Node := PostsList.AddChild(nil); // Добавляем строчку в таблицу
     // В зависимости от доступа и типа каждому элементу присваивается одна из иконок в TImageList. 
     // В ImageList всего загружено 9 иконок размером 16х16.
     Node.ImageIndex := Results.Items[i].ElementType;
     Node.Texts[0] := Results.Items[i].ElementName; // название должности
    End;

  Results.Free;

 PostsList.EndUpdate;

 ShowMessage(FloatToStr(GetTickCount - c) + ' мсек'); // выводим результат, сколько заняло построение таблицы
End.

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

Запаковуем EXE и измеряем производительность:

Чистый EXE без VMProtect - 3,226 сек.
EXE с VMProtect 2.12 без защиты ресурсов - 3,328 мсек
EXE с VMProtect 3.0 без защиты ресурсов - 3,282 мсек
EXE c VMProtect 2.12 и защитой ресурсов - 10,688 мсек
EXE c VMProtect 3.0 и защитой ресурсов - прождал более 5 мин, и закрыл программу

Как видно тормозит именно защита ресурсов, остальные измерения в пределах погрешности.
Выдвигаю предположение, что тормоза из-за работы с иконками. Отвязываю таблицу от TImageList для чистоты эксперемента.
Теперь в таблице никакая графика не используеются. Перекомпилирую EXE и делаю повторные замеры:

Чистый EXE без VMProtect - 3,719 сек.
EXE c VMProtect 2.12 и защитой ресурсов - 12,859 мсек
EXE c VMProtect 3.0 и защитой ресурсов - снова не дождался

Как видим дело не в иконках.
Второе предположние - дело в скинах DevExpress, возможно при построении таблицы идёт запрос к каким-то графическим элементам, которые VMProtect зашифровал.
Отключаю скины (ставлю в настройках NativeStyle = TRUE) и перекомпилирую проект.

Чистый EXE без VMProtect - 3,250 сек.
EXE c VMProtect 3.0 и защитой ресурсов - без изменений

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

После дальнейших экспериментов нашёл конкретный кусок кода, который томозит. Вот он:

  
 if PostsList.Count > 0 then
    Begin
      for i := PostsList.Count - 1 downto 0 do
        for z := PostsList.Count - 1 downto 0 do
          if (PostsList.Items[i].StateIndex = PostsList.Items[z].StateIndex) and
            (i <> z) then
            PostsList.Items[i].StateIndex := -1;

      for i := PostsList.Count - 1 downto 0 do
        if PostsList.Items[i].StateIndex = -1 then
          PostsList.Items[i].Delete;
    End;

В этом куске я просто проверяю чтобы элементы в таблице не дублировались, нахожу дубли и удаляю. В StateIndex хранится не иконка, а ID элемента, мне так удобнее.
Да, код немного топорный, но проблем с быстродействием не возникало. Каким образом шифрование ресурсов на него влияет непонятно.

Естественно, я могу искать дубликаты более эффективно, например, при помощи TStringList и dupIgnore, но всёже, мне кажеться, что в любом случае, VMProtect не должен замедлять выполнение данного куска кода более чем в 100 раз.

Присылайте минимальный пример (оригинальный EXE+VMP+MAP файлы), на котором можно воспроизвести проблему.

Отправил пример на почту

В отладчике видно, что программа постоянно формирует текст ошибки “Index %d out of bounds”. Почему она не показывается пользователю - вопрос или к вам или к разработчикам грида. В процессе формирования текста ошибки идет запрос к ресурсам в результате получаются вот такие тормоза.

Стек вызовов:
Unit1.TForm1.cxButton1Click
cxTL.TcxCustomTreeList.GetItem
cxTL.TcxTreeListNode.GetItem
dxCore.cxGetResourceString
System.LoadResString

Поставьте точку останова в незащищенном приложении на System.LoadResString и по стеку посмотрите из-за чего идет обращение к невалидному индексу.

 for i := 0 to cxTreeList1.Count - 1 do
  for z := 0 to cxTreeList1.Count - 1 do
   if cxTreeList1.Items[i].StateIndex = cxTreeList1.Items[z].StateIndex
    then
     Begin
      cxTreeList1.Items[i].StateIndex := -1;
     End;

Тормоза возникают конкретно здесь: if cxTreeList1.Items_.StateIndex = cxTreeList1.Items[z].StateIndex_

Спасибо большое за ответ. Вы абсолютно правы! Ошибка в компоненте DevExpress. Код этого компонента очень запутанный, я не смог найти причину, по которой возникает ошибка, но зато я устранил тормоза, зайдя в файл cxTL.pas и в процедуре TcxTreeListNode.GetItem закоментировал строчку:

  cxError((AIndex < 0) or (AIndex >= FCount),  cxGetResourceString(@scxIndexOutOfBounds), [AIndex]); // <!-- здесь происходит загрузка ресурсов, которая тормозит

  if Parent <> nil then
    Parent.AdjustIndexes;
  if (FLast.Index shr 1) <= AIndex  then
  begin
    Result := FLast;
    while Result.FIndex <> AIndex do
      Result := Result.FPrev;
  end
  else
  begin
    Result := FFirst;
    while Result.FIndex <> AIndex do
      Result := Result.FNext;
  end;

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

На самом деле можно было не лазить в чужие компоненты. Достаточно завести локальный ObjectList под создаваемые ноды и бегать уже по нему (шаблонный тип для TObjectList добавить по вкусу):

var Nodes: TObjectList;
...
  Nodes := TObjectList.Create();
  for i := 0 to 300 do
   Begin
    Node := cxTreeList1.AddChild(nil);
    Node.StateIndex := i;
    Node.Texts[0] := 'Элемент ' + IntToStr(i);

    Nodes.Add(Node);
   End;

 for i := 0 to Nodes.Count - 1 do
  for z := 0 to Nodes.Count - 1 do
   if Nodes[i].StateIndex = Nodes[z].StateIndex
    then
     Begin
      Nodes[i].StateIndex := -1;
     End;

  Nodes.Free;