Противоречивые знаки в сборке x86: movsx, то беззнаковое сравнение / ответвление? - PullRequest
0 голосов
/ 08 мая 2018

Я запутался в следующем фрагменте:

movsx   ecx, [ebp+var_8] ; signed move
cmp     ecx, [ebp+arg_0]
jnb     short loc_401027 ; unsigned jump

Это похоже на конфликт. Var_8, кажется, подписан на счет того, что это стало расширенным знаком. Тем не менее, jnb подразумевает, что var_8 не подписано в учетной записи, это сравнение без знака.

Итак, var_8 подписан или не подписан? А как насчет arg_0?

Ответы [ 3 ]

0 голосов
/ 08 мая 2018

Добавление к ответу Анатолия:

В принципе, на уровне сборки нет конфликта.

Информация в компьютере кодируется в битах (один бит = ноль или один), иecx - это 32 бита информации, больше ничего.

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

Наличие конфликта на логическом уровне зависит от запланированной функциональности автора.Если автор действительно хотел, чтобы этот тест против arg_0 не ветвился, если var_8 является "отрицательным" значением и arg_0 <2 <sup>31 , то код правильный.

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

Итак, подписано ли var_8 илибез знака?А как насчет arg_0?

var_8 является первым и главным адресом памяти, и оттуда используется 8 или 16 бит информации (непонятно из вашей разборки, какой именно) - в "подписано" путь.Но трудно рассказать больше о var_8 без изучения полного кода, это может быть даже var_8 32-битная беззнаковая int "переменная", но по какой-то причине автор решает использовать только расширенные синг-младшие 16 битсодержание в этом первом movsx.arg_0 затем используется как 32-разрядное целое число без знака для инструкции cmp.

В сборке вопрос не столько в том, является ли var_8 со знаком или без знака, вопрос в сборке состоит в том, сколько битовинформация, которая у вас есть и где, и как интерпретировать эти биты с помощью следующего кода.

В этом гораздо больше свободы, чем в C или других языках программирования высокого уровня, например, если в памяти имеется четыре байтовых счетчика.Вы знаете, что для каждого из них меньше 200, и вы хотите увеличить первый и последний из них, вы можете сделать это:

.data
counter1: db 13
counter2: db 6
counter3: db 34
counter4: db 17

.text
    ...
    ; increment first and last counter in one instruction
    ; overflow not-expected/handled, counters should to be < 200
    add  dword [counter1],0x01000001

Теперь (представьте себе), как вы будете интерпретировать это при разборе такого кода, не имея оригинальные комментарии из источника выше?Будет сложно, если из другого кода вы не поймете, что counter1-4 используются как отдельные счетчики байтов, и это оптимизация скорости для увеличения двух из них в одной инструкции.

0 голосов
/ 08 мая 2018

Это может быть результатом такой проверки диапазона, при которой нижняя граница ограничена не только 0, но и целыми значениями

int8_t var_8 = ...;
if (LOWER_BOUND <= var_8 && var_8 <= UPPER_BOUND)

Вышеупомянутое выражение может быть оптимизировано в

unsigned arg_0 = UPPER_BOUND - LOWER_BOUND;
if ((unsigned)(var_8 - LOWER_BOUND) <= arg_0)

с uint32_t arg_0 = UPPER_BOUND - LOWER_BOUND

Это хитрость, чтобы определить, находится ли целое число между двумя целыми числами (включительно) с известными наборами значений .

Большинство современных компиляторов уже знают, как выполнить эту оптимизацию, когда границы являются константами , например . Например, gcc выдаст приведенные ниже инструкции для первого фрагмента выше

    add     edi, -LOWER_BOUND 
    cmp     dil, UPPER_BOUND - LOWER_BOUND
    jbe     .L5
0 голосов
/ 08 мая 2018

Как отметил Шут, сравнение без знака может использоваться для проверки диапазона для чисел со знаком. Например, общее выражение C, которое проверяет, находится ли индекс между 0 и некоторым пределом:

short idx = ...;
int limit = ...; // actually, it's called "arg_0" - is it a function's argument?
if (idx >= 0 && idx < limit)
{
    // do stuff
}

Здесь idx после расширения знака - это 32-разрядное число со знаком (int). Идея состоит в том, что при сравнении с limit, как если бы он был без знака, он выполняет оба сравнения одновременно.

  1. Если idx положительно, то «подписано» или «без знака» не имеет значения, поэтому без знака Сравнение дает правильный ответ.
  2. Если idx отрицательно, то при интерпретации его как числа без знака будет получено очень большое число (больше 2 31 -1), поэтому в этом случае сравнение без знака также дает правильный ответ .

Таким образом, одно беззнаковое сравнение выполняет работу двух знаковых сравнений. Это работает только тогда, когда limit подписано и неотрицательно. Если компилятор может доказать, что он неотрицателен, он сгенерирует такой оптимизированный код.


Другая возможность, если исходный код на C содержит ошибки и сравнивает подписанный с unsigned. Несколько удивительной особенностью C является то, что при сравнении знаковой переменной с беззнаковым результатом является сравнение без знака.

short x = ...;
unsigned y = ...;

// Buggy code!
if (x < y) // has surprising behavior for e.g. x = -1
{
    // do stuff
}

if (x < (int)y) // better; still buggy if the casting could overflow
{
    // do stuff
}
...