Подтвердить что ты не робот

СПЕЦИФИКАЦИЯ

Я пытаюсь перепроектировать двоичный код, и следующая инструкция меня сбивает с толку, может ли кто-нибудь выяснить, что именно это делает?

=>0x804854e:    repnz scas al,BYTE PTR es:[edi]
  0x8048550:    not    ecx

Где:

EAX: 0x0
ECX: 0xffffffff
EDI: 0xbffff3dc ("aaaaaa\n")
ZF:  1

Я вижу, что это как-то уменьшает ECX на 1 каждую итерацию и что EDI увеличивается по длине строки. Я знаю, что он вычисляет длину строки, но насколько точно это происходит, и почему "al" участвует, я не совсем уверен.

4b9b3361

Ответ 1

Я попытаюсь объяснить это, вернув код обратно в C.

Справочник по набору инструкций Intel (том 2 из Руководство для разработчиков программного обеспечения) неоценим для такого рода обратного проектирования.

REPNE SCASB

Логика для REPNE и SCASB в сочетании:

while (ecx != 0) {
    temp = al - *(BYTE *)edi;
    SetStatusFlags(temp);
    if (DF == 0)   // DF = Direction Flag
        edi = edi + 1;
    else
        edi = edi - 1;
    ecx = ecx - 1;
    if (ZF == 1) break;
}

Или проще:

while (ecx != 0) {
    ZF = (al == *(BYTE *)edi);
    if (DF == 0)
        edi++;
    else
        edi--;
    ecx--;
    if (ZF) break;
}

Длина строки

Однако приведенного выше недостаточно, чтобы объяснить, как он вычисляет длину строки. Основываясь на наличии not ecx в вашем вопросе, я предполагаю, что фрагмент принадлежит этой идиоме (или подобной) для вычисления длины строки с помощью REPNE SCASB:

sub ecx, ecx
sub al, al
not ecx
cld
repne scasb
not ecx
dec ecx

Перейдя на C и используя нашу логику из предыдущего раздела, получим:

ecx = (unsigned)-1;
al = 0;
DF = 0;
while (ecx != 0) {
    ZF = (al == *(BYTE *)edi);
    if (DF == 0)
        edi++;
    else
        edi--;
    ecx--;
    if (ZF) break;
}
ecx = ~ecx;
ecx--;

Упрощение использования al = 0 и DF = 0:

ecx = (unsigned)-1;
while (ecx != 0) {
    ZF = (0 == *(BYTE *)edi);
    edi++;
    ecx--;
    if (ZF) break;
}
ecx = ~ecx;
ecx--;

Примечания:

  • в двухзначной нотации, переворачивание бит ecx эквивалентно -1 - ecx.
  • в цикле ecx уменьшается до того, как цикл прерывается, поэтому он уменьшает на length(edi) + 1 в целом.
  • ecx никогда не может быть нулевым в цикле, поскольку строка должна занимать все адресное пространство.

Итак, после цикла выше ecx содержит -1 - (length(edi) + 1), который совпадает с -(length(edi) + 2), который мы переворачиваем биты, чтобы дать length(edi) + 1, и, наконец, декремент, чтобы дать length(edi).

Или перестроить цикл и упростить:

const char *s = edi;
size_t c = (size_t)-1;      // c == -1
while (*s++ != '\0') c--;   // c == -1 - length(s)
c = ~c;                     // c == length(s)

И инвертирование счета:

size_t c = 0;
while (*s++ != '\0') c++;

который является функцией strlen из C:

size_t strlen(const char *s) {
    size_t c = 0;
    while (*s++ != '\0') c++;
    return c;
}

Ответ 2

AL, потому что scas сканирует память для значения AL. AL был обнулен, чтобы команда находила нулевой конец в конце строки. scas сам увеличивает (или уменьшает, в зависимости от флага направления) EDI автоматически. Префикс REPNZ (который более читается в форме REPNE) повторяет scas, пока сравнение ложно (REP есть, а N ot E) и ECX > 0. Он также автоматически уменьшает ECX на каждой итерации. ECX был инициализирован до самой длинной строки, так что он не заканчивает цикл раньше.

Так как ECX отсчитывает от 0xffffffff (также известный как -1), результирующая длина будет -1-ECX, которая из-за особенности 2-арифметической суммы может быть вычислена с помощью инструкции NOT.

Ответ 3

Он сравнивает байт в es:[edi] с тем, что входит в al, и повторяет этот шаг до тех пор, пока либо ecx не станет равным нулю, либо значение в es:[edi] не будет соответствовать значению в al. После каждого шага edi увеличивается, поэтому указывает на следующий байт в памяти. Затем программа применяет not к счетчику (ecx) на основе следующей инструкции.

repnz означает "повторять до тех пор, пока флаг нуля не будет установлен, а cx не равен нулю". Каждая итерация уменьшается ecx. scas или более точно scasb сравнивает значение в al с операндом памяти (всегда es:[edi] или es:[di] в зависимости от размера адреса), а затем устанавливает флаги соответственно (флаг нуля будет установлен, если два значения равно) и приращения (или декременты, основанные на флагом направления) edi.