Как мы будем действовать
Поскольку, написание своего собственного профилировщика отнимает много сил и времени, на первых порах можно ограничиться использованием отладчика, поддерживающего трассировку в лог и обходящего большинство типовых защит. На эту роль хорошо подходит знаменитый OllyDbg для которого существует множество плагинов, скрывающих присутствие отладчика от глаз практически всех защитных механизмов.
Запуская программу до и после срабатывания защиты, мы получаем два лога трассировки. Считываем адреса машинных команд, загоняем их в массив "своего" лога, сортируем, убираем повторы, а затем сравниваем оба массива. Легко поискать, что сравнение сводится к поиску, который в отсортированном массиве быстрее всего осуществляется методом "вилки", причем сравнивать нужно не только первый массив со вторым, но и второй с первым, для удобства выводя рядом с адресами мнемоники машинных команд, которые можно вытащить прямо из лога.
Ниже приведен листинг упрощенной версии анализатора логов, не использующей сортировку и выполняющий сравнение путем последовательного поиска, а потому объемные логи обрабатывающий довольно долго (впрочем, с учетом быстродействия современных процессоров времени потребуется не так уж и много):
#define XL 1024 // максимальная длина одной строки лога
#define NX (1024*1024) // максимальное количество строк лога
// добавить новый адрес в массив,
// если он не встречался ранее
addnew(unsigned int *p, unsigned int x)
{
int a;
for (a = 1; a<*p; a++) if (p[a] == x) return 0; if (a == NX) return -1;
p[0]++; p[a] = x; return 1;
}
// вывод результатов на экран
PRINT(unsigned int x, FILE *f)
{
char *z; char buf[XL];
while(fgets(buf,XL-1,f)) if((strtol(buf,&z,16)==x)&&(printf("%s",buf)|1))break;
}
// сравнение двух массивов адресов на предмет различий
diff(unsigned int *p1, unsigned int *p2, FILE *f)
{
int a, b, flag;
for (a = 1; a<*p1; a++)
{
for(b = 1, flag = 0;b<*p2; b++) if ((p1[a]==p2[b]) && ++flag) break;
if (!flag) PRINT(p1[a],f);
}
}
main(int c, char **v)
{
int f=0; char buf[XL]; FILE *f1, *f2; unsigned int *p1, *p2; char *x;
if (c < 3) return printf("USAGE: log-coverage-diff.exe file1 file2\n");
p1 = (unsigned int*) malloc(NX*4); p2 = (unsigned int*) malloc(NX*4);
f1 = fopen(v[1],"rb");if (!f1) return printf("-ERR: open %s\x7\n",v[1]);
f2 = fopen(v[2],"rb");if (!f2) return printf("-ERR: open %s\x7\n",v[2]);
fgets(buf, 1023, f1); fgets(buf, 1023, f2);
while(f<2 && !(f=0))
{
if (fgets(buf, XL-1, f1)) addnew(p1,strtol(buf,&x,16)); else f++;
if (fgets(buf, XL-1, f2)) addnew(p2,strtol(buf,&x,16)); else f++;
}
if (fseek(f1, 0, SEEK_SET)) return printf("-ERR: seek %s\x7\n",v[1]);
if (fseek(f2, 0, SEEK_SET)) return printf("-ERR: seek %s\x7\n",v[2]);
printf("\ndiff p1 -> p2\n");diff(p1,p2,f1);
printf("\ndiff p2 -> p1\n");diff(p2,p1,f2);
return 0;
}
Листинг 1 листинг программы log-coverage-diff.c, сравнивающий протоколы трассировки отладчика OllyDbg и определяющий разницу в покрытии
Компиляция осуществляется как обычно, то есть с ключами по умолчанию, в частности, при использовании Microsoft Visual C++ командная строка выглядит так: "cl.exe log-coverage-diff.c", а для создания оптимизированного варианта — "cl.exe /Ox log-coverage-diff.c" Только не надо выделять весь текст и копировать его в среду разработки, а потом нажимать F7 (Build) и удивляться почему это программа не компилируется! Она и не должна компилироваться, поскольку по умолчанию Microsoft Visual Studio создает проект на Си++, а это Си.Как говорится, почувствуйте разницу!
Рисунок 4 результат компиляции log-coverage-diff.c в Microsoft Visual Studio IDE – 3 ошибки, 6 предупреждений
Рисунок 5 а из командной строки компиляция проходит нормально!
На всякий случай (вдруг у кого под рукой не окажется компилятора) готовый исполняемый файл идет на прилагаемом диске.