Показаны сообщения с ярлыком Assembler. Показать все сообщения
Показаны сообщения с ярлыком Assembler. Показать все сообщения

воскресенье, 27 февраля 2011 г.

Отличия ADDR и OFFSET

Проясним момент насчет различия операторов ADDR и OFFSET в программах, написанных на Ассемблере.
Первоочередное назначение данных операторов - это получение адреса переменной в памяти.
В программах на ассемблере могут присутствовать как локальные так и глобальные переменные.
Глобальные переменные существуют в памяти на протяжении всего времени выполнения программы, локальные - только во время выполнения процедуры, в которой они определены, и удаляются как только она завершит работу. Поэтому адреса глобальных переменных известны уже на стадии ассемблирования, тогда как адреса локальных переменных в стеке станут известны только во время выполнения программы, а точнее при выполнении процедуры, в которой эти переменные объявлены.
Что касается операторов, то OFFSET вычисляет адрес уже размещенных к началу выполнения программы переменных, а это значит что данный оператор можно использовать для вычисления адресов только глобальных переменных.
С помощью оператора ADDR можно получить адрес локальной переменной.
Но почему так получается, что с помощью ADDR можно узнать адрес локальной пременной, а с помощью OFFSET - нельзя. Дело в том, что ассемблер, встретив оператор ADDR, использующийся с локальной переменной, генерирует последовательность инструкций
lea eax, localVar
push eax
ну а инструкция lea уже делает свое дело. Т.к. ADDR превращается в выше указанную посдедовательность инструкций, данный оператор можно использовать только с директивой invoke для передачи адреса переменной в качестве параметра. Присваивание регистру полученного таким способом адреса невозможно. ADDR можно так же использоваться и с глобальными переменными. В этом случае ассемблер сгенерирует
push offset globalVar
И в отличие от OFFSET, ADDR не умеет вычислять смещения меток, определенных далее в коде.
mov eax, offset l1 ; так можно
invoke Foo, ADDR l1; так нельзя
l1:
xor ebx, ebx
invoke Foo, ADDR l1; так можно

среда, 26 января 2011 г.

EFLAGS. Перенос (CF) и переполнение (OF)

Есть в процессоре (будем говорить об архитектуре Intel) такой замечательный регистр - EFLAGS или регистр флагов. Данный 32-битный регистр содержит группу статусных флагов, группу флагов контроля и группу системных флагов. Как правило, в учебниках по архитектуре компьютера или ассемблеру присутствует довольно сухое описание, для чего определенный флаг предназначен, и еще чаще отсутствуют примеры, связанные с данным флагом. Рассмотрим два важных флага - флаг переноса (carry flag - CF) и флаг переполнения (overflow flag - OF) и постараемся более подробно осветить эту тему, чем в обычно это делают в учебниках.
Из руководства "Intel Software developer`s manual vol.1" (перевод):
CF - устанавливается, если в результате арифметической операции произошло переполнение разрядной сетки либо заем из старшего бита. Флаг свидетельствует о переполнении для беззнаковых чисел.

Будем рассматривать все на примере 8-битных чисел/регистров.
С переполнением разрядной сетки все достаточно просто. Диапазон беззнаковых чисел, которые могут быть представлены 8 битами - это 0...255. Следовательно переполнение наступит, если результатом выполнения операции будет число больше 255.

    clc
    mov al, 250
    mov bl, 10
    add al, bl ; должно быть 260, но на деле в al 4

В данном случае число не помещается в 8 бит, и возникает переполнение.

Рассмотрим числа 8 и 6. Что будет если из 6 вычесть 8? В двоичном представлении 6 имеет вид 00000110, 8 - 00001000. При поразрядном вычитании в 4 бите произойдет заем, который породит заем из 5,6,7,8 битов. Т.е. в итоге происходит заем из старшего бита, а это значит что флаг переноса устанавливается.

    clc
    mov al, 6
    mov bl, 8
    sub al, bl ; теперь в al -2

Для чего в данном случае устанавливается флаг. Дело в том, что результат получается отрицательным, что логично, но это неверно для беззнаковых чисел, которые по определению не могут иметь отрицательного значения. Т.е. результат опять не входит в диапазон 0...255 Из этого можно сделать вывод - если происходит вычитание большего числа из меньшего, то флаг переноса будет установлен, т.к. в этом случае происходит заем из старшего бита.
А при сложении отрицательного и положительного (минус на минус дает плюс) и получая по сути тот же результат, флаг переноса устанавливаться не будет, несмотря на то, что результат является слишком малым и не входит в диапазон 0...255. Дело в том, что отсутствует заем из старшего бита.
    mov al, -79
    mov bl, 49
    add al, bl ; CF = 0
Но если взять вместо -79 число -49, то флаг переноса установится, т.к. -49 для беззнаковых чисел равно 207, а 207+49=256, что является переполнением 8 бит.
Флаг переноса полезен при работе с большими числами, не помещающимися в 32-разрядный регистр. Например, если требуется сложить 2 числа, каждое из которых расположено в паре 32-рязрядных регистров.

Рассмотрим флаг переполнения.
Из руководства "Intel Software developer`s manual vol.1" (перевод):
Флаг OF устанавливается, если число - слишком большое положительное, либо слишком маленькое отрицательное. Флаг свидетельствует о переполнении для знаковых чисел.
Диапазон знаковых чисел в 8-битном случае -128...127, т.е., как сказано в руководстве, 2 варианта:
  • Число слишком мало для представления в виде 8-битного знакового
  • Число слишком велико для представления в виде 8-битного знакового

Первый случай:
    mov al, 117
    mov bl, 92
    add al, bl ;
В итоге имеем -47 вместо 209 из-за переноса в знаковый бит, т.е. число 209 выходит за допустимый диапазон сверху.
Второй случай:
    mov al, -20
    mov bl, -118
    add al, bl ;
Складываем 2 отрицательных числа и получаем тем не менее положительную сумму (118 вместо -138), т.к. -138 выходит за допустимый диапазон снизу.
Но что такое "знаковые числа"? Как процессор понимает, когда используется знаковое, а когда - беззнаковое число. На самом деле процессор не знает этого. Он сразу предполагает оба случая и в соответствии с этим выставляет флаги.