Что такое оператор OFFSET в ассемблере. Для чего он используется. Как правильно применять OFFSET в программах на ассемблере. Какие преимущества дает использование OFFSET.
Что такое оператор OFFSET в ассемблере
Оператор OFFSET в ассемблере используется для получения смещения (адреса) переменной или метки относительно начала сегмента. Он возвращает 16-битное или 32-битное значение, представляющее расстояние в байтах от начала сегмента до указанной переменной или метки.
Основные особенности оператора OFFSET:
- Возвращает числовое значение, а не сам адрес
- Работает только с переменными и метками в текущем сегменте
- Может использоваться с данными и кодом
- Часто применяется для передачи адресов в подпрограммы
Для чего используется оператор OFFSET
Оператор OFFSET применяется в ассемблерных программах для следующих целей:
- Получение адреса переменной для передачи в подпрограмму
- Вычисление расстояния между двумя метками или переменными
- Определение размера блока данных или кода
- Создание таблиц адресов для косвенных переходов
- Организация доступа к элементам массива по индексу
Таким образом, OFFSET позволяет манипулировать адресами элементов программы, не обращаясь к ним напрямую.

Синтаксис использования оператора OFFSET
Общий синтаксис применения оператора OFFSET выглядит следующим образом:
OFFSET identifier
Где identifier — это имя переменной, массива, процедуры или метки, для которой нужно получить смещение.
Примеры использования:
mov ax, OFFSET variable ; Загрузить смещение переменной в AX
lea dx, [OFFSET array] ; Загрузить адрес массива в DX
call OFFSET procedure ; Вызвать процедуру по смещению
Оператор OFFSET можно применять как в сегменте кода, так и в сегменте данных программы.
Преимущества использования оператора OFFSET
Применение оператора OFFSET в ассемблерных программах дает ряд преимуществ:
- Позволяет работать с адресами независимо от сегментации памяти
- Упрощает доступ к элементам массивов и структур данных
- Облегчает передачу адресов между процедурами
- Делает код более переносимым между разными моделями памяти
- Помогает оптимизировать размер исполняемого файла
За счет этих преимуществ OFFSET широко применяется в системном программировании и разработке драйверов.

Примеры использования оператора OFFSET
Рассмотрим несколько практических примеров применения оператора OFFSET в ассемблерных программах:
Пример 1: Передача адреса строки в процедуру
.data message db 'Hello, World!',0 .code main PROC push OFFSET message ; Передать адрес строки call print_string ; Вызвать процедуру печати ret main ENDP print_string PROC ; Тело процедуры ret print_string ENDP
Пример 2: Вычисление размера массива
.data
array db 10, 20, 30, 40, 50
.code
main PROC
mov eax, OFFSET array_end
sub eax, OFFSET array ; EAX = размер массива
ret
main ENDP
array_end:
Пример 3: Доступ к элементам массива по индексу
.data
numbers dw 100 dup(?)
.code
main PROC
mov ebx, OFFSET numbers
mov eax, [ebx + 4*esi] ; Загрузить i-й элемент
ret
main ENDP
В этих примерах оператор OFFSET используется для получения адресов переменных и меток, что позволяет эффективно работать с данными в памяти.
Ограничения и особенности оператора OFFSET
При работе с оператором OFFSET следует учитывать некоторые ограничения и особенности:

- OFFSET возвращает смещение относительно начала текущего сегмента
- Нельзя использовать OFFSET для получения адреса регистра
- В 16-битном режиме OFFSET возвращает 16-битное значение
- В 32-битном режиме OFFSET возвращает 32-битное значение
- OFFSET не работает с динамически выделенной памятью
Важно помнить эти ограничения при разработке ассемблерных программ с использованием оператора OFFSET.
Альтернативы оператору OFFSET
Хотя оператор OFFSET очень удобен, в некоторых случаях можно использовать альтернативные способы получения адресов:
- Команда LEA (Load Effective Address) для вычисления адреса
- Прямая загрузка адреса с помощью MOV
- Использование сегментных регистров для доступа к данным
- Применение указателей и индексной адресации
Выбор конкретного метода зависит от архитектуры процессора, модели памяти и требований к производительности программы.
Заключение
Оператор OFFSET является мощным инструментом в арсенале ассемблерного программиста. Он позволяет эффективно работать с адресами переменных и меток, упрощая доступ к данным и организацию кода. Правильное использование OFFSET помогает создавать более гибкие и оптимизированные программы на ассемблере.

Основные выводы об операторе OFFSET:
- Возвращает смещение относительно начала сегмента
- Широко применяется для работы с адресами и массивами
- Упрощает передачу параметров в подпрограммы
- Делает код более переносимым между разными моделями памяти
- Требует понимания особенностей работы с адресами в ассемблере
Освоение техники использования оператора OFFSET — важный шаг в изучении ассемблерного программирования, открывающий новые возможности для создания эффективного низкоуровневого кода.
Вопрос для тех, кто знает ассемблер
← →
Udjin
(2002-05-06 16:36) [0]
Делаю контрольную работу на ассемблере (чайник я в этом деле). Нужно определить номер версии DOS, программа по условию резидентная. Вот что получилось
CSEG segment
assume cs:CSEG, ds:CSEG
org 100h
start: jmp Myload ;Переход на выпонение загрузочной части
key dw 5555h ;Ключ для определения загружен ли резидент
oldvect dd ? ;здесь запоминается адрес правильного
;обработчика прерывания 09h
highDos db ? ;здесь хранится старшая цифра версии Dos
lowDos db ? ;здесь младшая старшая цифра версии Dos
mes db «MsDos $» ;собщение при выводе номера версии
point db «.$» ;разделение старшей и младшей цифры версии
new proc ;начало резидентной части
push ax ;сохраняем в стеке регистры
push es
push bx
push cx
push dx
in ax,60h ;Чтение порта клавиатуры в AX
jnz exit ;если нет, то возврат управления
mov ax,0 ;Чтение флагов состояния
mov es,ax ;клавиатуры в регистр AL и
mov al,es:[0417h] ;определение нажаты ли клавиши
and al,00001100b ;Ctrl-Alt,если нет, то
cmp al,00001100b ;возврат управления
jnz exit
in al,61h ;Освобождене
or al,80h ;буфера
out 61h,al ;клавиатуры от
and al,7Fh ;скан кода
out 61h,al
mov cx,6 ;вывод слова MsDos
mov bx,offset mes
m1: mov ah,0eh
mov al,cs:[bx]
int 10h
inc bx
loop m1
mov bx,offset highDos ;вывод старшей цифры версии Dos
mov ah,0eh
mov al,cs:[bx]
int 10h
mov bx,offset point ;вывод разделяющей точки
mov al,cs:[bx]
int 10h
mov bx,offset lowDos ;вывод младшей цифры версии Dos
mov ah,0eh
mov al,cs:[bx]
int 10h
exit:
pop dx ;восстановление регистров
pop cx
pop bx
pop es
pop ax
jmp cs:oldvect;передача управления оригинальному обработчику
new endp
Myload:
;начало загрузочной части
cmp word ptr es:[103h],5555h
je inst
mov ah,30h ;получение версии Dos
int 21h
mov highDos,al
mov lowDos,ah
add highDos,30h ;смещение до соответствующих цифр
add lowDos,30h
mov ah,35h ;Чтение вектора 09h
mov al,09h
int 21h
mov word ptr oldvect,bx ;сохранение вектора прерывания
mov word ptr oldvect+2,es
mov al,09h
mov dx,offset new ;резидентной процедуры old
int 21h
mov dx,offset Myload ;Адрес конца резидентной части
int 27h ;KEEP
inst:
mov ah,9;Если в памяти, то выведем соответствующее сообщение
mov dx,offset mes2 ;
int 21h
mov ah,4ch ;возврат управления
int 21h ;
mes2 db «already loaded $»;сообщение если резидент в памяти
CSEG ends
end start
Возникли следующие вопросы
1. Запретить повторный запуск резидента не удаётся, хотя как это делается в книжке подсмотрел
2. Как вывести номер версии если он окажется, например, 6.22
Моя программа в этом случае не сработает
3. Как на Ассемблере определить скорость процессора (это к другой контрольной)
P.S. Всё это само-собой под Dos
← →
copyr25
(2002-05-06 16:53) [1]
Могу выслать .rar . SysInfo107. Где скачал — уже не помню:)) Версию ОС точно, определяет.
И много всего тоже определяет. Работает и под DOS и в окошке под Windows.
C исходниками. Пишите письма:))
← →
Udjin
(2002-05-06 16:58) [2]
Адрес мой [email protected]
← →
copyr25
(2002-05-06 17:21) [3]
Ещё в прошлом веке мне однажды пришлось сделать
программку для управления магнитофоном от «мышиного»
на двух дырочках доступны 00 01 10 11 :))
.

page ,132
;нажатие F12 подает +5В на 7-ю ножку RS-232
;нажатие F11 отключает этот потенциал
;обрабатывается попытка повторной загрузки
;запуск с ненулевым хвостом выгружает резидент из памяти
CSEG segment
assume cs:cseg,ds:cseg,es:cseg
org 100h
f10 proc
jmp init
old09h dd 0 ; буфер для сохранения старого вектора 9h
old2fh dd 0 ; буфер для сохранения старого вектора 2fh
flag db 0 ; буфер флага «выгружать из памяти»
new2fh proc ; ПОП 2fh — связь с резидентными программами
cmp ah,0c8h ; наша функция прерывания?
jne out2fh ; нет
cmp al,0 ; да, попытка повторной загрузки?
je inss ; да, обработаем запрет
cmp al,1 ; требование на выгрузку?
je unins ; да
jmp short out2fh ; ни то, ни другое — не обрабатываем
inss: mov al,0ffh ; покажем, что программа уже загружена
iret ;
out2fh: jmp cs:old2fh ; переход в след.

; выгрузим программу из памяти, предварительно восстановив все
; перехваченные ею векторы (9h и 2fh)
unins: push ds es dx ; сохраним исп.регистры
mov ax,2509h ; установить вектор 09h
lds dx,cs:old09h ; адрес старого вектора
int 21h ; восстановили 09h
mov ax,252fh ; установить вектор 2fh
lds dx,cs:old2fh ; адрес старого вектора
int 21h ; восстановили 2fh
mov es,cs:2ch ; получим из PSP адрес собственного
mov ah,49h ; окружения резидента и выгрузим его
int 21h ;
push cs ; выгрузим теперь саму программу
pop es ;
mov ah,49h ;
int 21h ;
pop dx es ds ;
iret ;
new2fh endp
new09h proc ; ПОП 09fh — прерывание от клавиатуры
push ax ;
in al,60h ;
cmp al,58h ; скен-код F12 ?
je yes ; да
cmp al,57h ; F11?
je yes
pop ax ;
jmp cs:old09h ; отдаемся старому вектору
yes:
push bx
mov bl,al ; сохраняем скен-код в bl
push dx
mov al,0 ; задаем параметры передачи COM1
or al,10011011B
mov ah,0
mov dx,0
int 14h
mov dx,3f8h+4 ; адрес регистра данных COM1
mov al,bl ;
sub al,56h ; 1 в al гасит 7-ю ножку, 2 — зажигает
out dx,al ; передаем
pop dx
mov al,bl
pop bx
in al,61h ; восстановим разрешение прерываний
or al,80h ; от клавиатуры
out 61h,al
and al,7fh
out 61h,al
mov al,20h
out 20h,al
pop ax
iret
new09h endp
endres=$ ;
f10 endp ;
init proc ;
mov cl,es:80h ; смотрим, был ли хвост при запуске
cmp cl,0 ; был?
je conti ; нет
inc flag ; установим флаг для выгрузки
conti: mov ah,0c8h ; вызов связи с резидентом с функц. c8h
mov al,0 ; смотрим, программа установлена?
int 2fh ;
cmp al,0ffh ;
je inst ; да, наша сигнатура
mov ax,352fh ; вектор 2fh
int 21h ;
mov word ptr cs:old2fh,bx ; сохраним
mov word ptr cs:old2fh+2,es ; в буфере
mov ax,252fh ;
mov dx,offset new2fh ; и укажем для него новую ПОП
int 21h ;
mov ax,3509h ; вектор 9h
int 21h ;
mov word ptr cs:old09h,bx ; сохраним
mov word ptr cs:old09h+2,es ; в буфере
mov ax,2509h ;
mov dx,offset new09h ; и укажем для него новую ПОП
int 21h ;
mov ah,9h ; сообщим, что F10 установлен
mov dx,offset mes ;
int 21h ;
mov ax,3100h ; завершим программу
mov dx,(endres-f10+10fh)/16 ; оставив ее резидентной
int 21h ;
inst: ;
cmp flag,1 ; выгружать?
je uin ; да
mov ah,9h ; сообщим, что
mov dx,offset mes1 ; программа УЖЕ загружена
int 21h ;
mov ax,4c01h ; и выйдем с errorlevel=1
int 21h ;
uin: mov ax,0c801h ; посылаем из вновь запущенной
int 2fh ; F10 резиденту в памяти
mov ah,09h ; признак для выгрузки (1)
mov dx,offset mes2 ; сообщаем, что программа
int 21h ; выгружается
mov ax,4c00h ; и выходим из вновь запущенной
int 21h ;
init endp
mes db «Драйвер управления магнитофоном загружен»,10,13
db «Клавиша F12 включает магнитофон»,10,13
db «Клавиша F11 — выключает»,10,13
db «Для выгрузки драйвера — набрать magnit off»,10,13,10,13
db «(C) copyr25@yahoo. com$»
mes1 db «Драйвер управления магнитофоном УЖЕ загружен$»
mes2 db «Драйвер управления магнитофоном выгружен из памяти$»
CSEG ends
end f10
Может, чем и поможет?
:))
← →
copyr25
(2002-05-06 17:52) [4]
[email protected]: Послал Вам .rar. Но там крутой asm:))) Имейте ввиду.
← →
copyr25
(2002-05-06 18:53) [5]
mov dx,offset Myload ;Адрес конца резидентной части
int 27h ;KEEP
Вот от этого и не работает.
В 1995 г. была такая книжка «Программируем на языке ассемблера IBM PC»
часть 2. Прикладное программирование. Издательство ЭНТРОП, Москва, 1995.
П. И.Рудаков, К.Г.Финогенов.
стр.115 «Резидентный обработчик прерывания от клавиатуры с подключением
после системного обработчика».
Я до сих пор эту книжку люблю. И 1-ю часть, и 3-ю. За работающие коды.
А Финогенов, ваще, ещё тот Мастер — он и в asm»e, и в web»e, и в Pascal»e многих
российских программеров обучил. Найдите книжку! Не пожалеете:))
← →
Udjin
(2002-05-07 08:43) [6]
to copyr25
благодарю за письмо. Попробую разобраться с крутым asm-ом :)). С ограничением запуска разобрался. Сначала переменной key присвоил зачение отличное от 5555h, а строки
cmp word ptr es:[103h],5555h
je inst
заменил на
mov ax,0
mov es,ax
cmp word ptr es:[102h],5555h
je inst
mov es:[102h],5555h
to All С последними 2-мя вопросами я пока не смог разобраться.
Дневники чайника
Дневники чайникаСтек.
Восьмой день
Стек — специально выделенная область памяти для передачи или сохранения данных.
Мы всё время наблюдаем такую форму записи (prax03.com):
00000000: B80300 mov ax,00003 00000003: CD10 int 010 00000005: B402 mov ah,002 00000007: 8B167501 mov dx,[0175] 0000000B: CD10 int 010 0000000D: FEC6 inc dh 0000000F: 80C203 add dl,003 00000012: 89167501 mov [0175],dx 00000016: B409 mov ah,009 00000018: BA5001 mov dx,00150 0000001B: CD21 int 021 0000001D: 803E760119 cmp b,[0176],019 00000022: 75E1 jne 000000005 00000024: B410 mov ah,010 00000026: CD16 int 016 00000028: CD20 int 020
Я имею в виду, что адреса растут вниз. 0 — выше всех (как первая строка в книге), 28 — нижняя строчка.
Так вот в этой системе отображения стек растёт вверх.
Дно стека находится по самому старшему адресу, а вершина — по самому младшему адресу.
На вершину стека указывает регистр-указатель ESP — это его назначение (Stack Pointer — указатель стека).
Видите, prax03 в строке 12h сохраняет значение регистра DX, а в строке 07 при следующем проходе цикла обратно восстанавливает это значение. В данном примере стек использовать неудобно, но если в программе нужно много раз сохранять разные регистры, то лучше делать это через стек. Для записи в стек есть команда PUSH.
Пример:
push EDX ; толкнуть в стек (положить в стек) ... pop EDX ; извлечь из стека в EDX (вытолкнуть в EDX)
Другой пример:
push EDX ; положить в стек ... pop EAX ; извлечь из стека в EAX (в EAX будет то, что было в EDX)
Причём значений в стек можно укладывать очень много.
push EAX ; положить в стек push EBX ; положить в стек push ECX ; положить в стек push EDX ; положить в стек push 01234h ; положить в стек ... pop ESI ; извлечь из стека (значение 01234h было сверху, значит, оно и выйдет) pop EDX ; извлечь из стека в EDX pop ECX ; извлечь из стека в ECX pop EBX ; извлечь из стека в EBX pop EAX ; извлечь из стека в EAX
Обратите внимание, что значения извлекаются в обратном порядке. Вершина выходит первой, а дно последним. На практике, слава Богу, мудрить со стеком придётся мало, но если вы не уловите принцип, то писать программы будет трудновато.
Матрос! Ну-ка иди сюда… Что тут у тебя в корзинке? О, завтрак! Так, сверху яйца, подержи, бутерброд, бутылка… подушка? А зачем тебе подушка? Ты что, на вахте спать собрался! А ну, отдай сюда корзину. Яйца-то сырые, чёрт тебя дери! И бутерброды измазал, и подушку испортил. Какого шлейфа ты их не сварил? Иди отсюда, сам знаю, что яйца сверху кладут.
Память под стек выделяет Windows.
Теперь осмысленно вернёмся к нашей первой программе под форточки:
Адреса байты имена операнды комментарии 00401000 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL 00401002 68 00304000 push 00403000 ; |Title = "It's the first your program for Win32" 00401007 68 21304000 push 00403021 ; |Text = "Assembler language for Windows is a fable!" 0040100C 6A 00 push 0 ; |hOwner = NULL 0040100E E8 07000000 call < jmp.&user32.MessageBoxA> ; \MessageBoxA 00401013 6A 00 push 0 ; /ExitCode = 0 00401015 E8 06000000 call < jmp.&kernel32.ExitProcess> ; \ExitProcess 0040101A FF25 08204000 jmp dword ptr ds:[< user32.MessageBo> ; user32.MessageBoxA 00401020 FF25 00204000 jmp dword ptr ds:[< kernel32.ExitPro> ; kernel32.ExitProcess
Команда Push кладёт в стек указанное в ней значение. Оно может быть два байта или четыре. В 32-битных программах это будет 32-битное значение, то есть dword (4 байта). Так что для нас теперь одно значение в стеке 4 байта.
Push 0 ; отправляет в стек значение 00000000 Push 00403000 ; отправляет в стек значение 00403000 Push 00403021 ; отправляет в стек значение 00403021 Push 0 ; отправляет в стек значение 00000000
Это, естественно, целое и для «удобства» чтения, младший байт уже справа, старший слева.
Матрос, ты запомнил, что на наших чертежах в целой боевой единице Бинарников байты строятся по старшинству слева направо?
Каждый младший — самый левый, каждый старший — самый правый (на фиг такие чертёжи :).
У них там в главном штабе есть ещё запутка — стек.
Он в чертежах идёт от дна наверх. Целые (word или dword) в нём укладываются именно так: от дна наверх.
Действительно, стек проще представлять в высоту, чем в одну строку. Вот что будет содержаться в стеке перед выполнением строки 40100Eh в нашей программе.
00000000 00403021 00403000 00000000 ........
Каждая строка здесь — одно значение в стеке. Понятие дно очень наглядно объясняет, как устроен стек.
Давайте удалим из исходника эту строку:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
И впишем вместо неё вот такие команды:
push 0AAAAAAAAh push 0BBBBBBBBh push 0CCCCCCCCh push 0DDDDDDDDh pop EAX pop ECX pop EDX pop EBX
Соберите программу ещё раз. Откройте в Olly.
То, что вы делали в отладчике CodeView клавишей F10, называется по-умному:
пошаговая трассировка исполняемого кода без захода в процедуры. В Olly такой «шаг» выполняет клавиша F8.
Адреса Байты Имена Операнды 00401000 68 AAAAAAAA push AAAAAAAA 00401005 68 BBBBBBBB push BBBBBBBB 0040100A 68 CCCCCCCC push CCCCCCCC 0040100F 68 DDDDDDDD push DDDDDDDD 00401014 58 pop eax 00401015 59 pop ecx 00401016 5A pop edx 00401017 5B pop ebx
Когда вы дойдёте до строки 401014h (F8 четыре раза), стек будет выглядеть вот так:
0012FFB4 DDDDDDDD 0012FFB8 CCCCCCCC 0012FFBC BBBBBBBB 0012FFC0 AAAAAAAA ... ...
Посмотрите в отладчике, обязательно! Нижняя правая часть.
Вы должны были понять, что каждое новое значение укладывается сверху. Получается, стек растёт вверх.
Получается, регистр ESP с каждым новым значением стека уменьшается.
И каждый раз, когда из стека извлекают значение, ESP увеличивается на 4 (или на 2 в 16-битных программах).
А ещё стек нужно выравнивать, иначе программа будет работать неправильно. После того, как ваши данные в стеке отработали или просто больше не нужны, возвращайте вершину в положение, которое она занимала раньше — это и есть выравнивание. Если вы так не сделаете, программа вызовет ошибку.
Стек, пожалуй, самое сложное понятие, которое нужно усвоить для написания программ на Ассемблере. Честно-честно, если вы поймёте это, значит, вы точно сможете освоить всё остальное. Конечно, нужна практика, помню, я три дня потратил на понимание стека. Калашников обманул, сказал, что это просто. Так и было, когда я его примеры использовал, а вот когда свои программки стал писать, стек вызвал наибольшие затруднения.
Три дня — думаю, это немного для самого сложного фокуса.
Чтоб положить что-то в стек, пишите:
push что-то
Происхождение | От англ.![]() |
Формат | push операнд |
Действие | Толкает значение в стек |
Примечание | Если нужно толкнуть в стек все 8 регистров общего назначения (EAX, EBX, ECX, EDX, EBP, ESP, ESI, EDI), то лучше использовать одну команду PUSHA/PUSHAD |
Чтоб достать число с «верхушки стека» куда-то, пишите:
pop куда-то
Происхождение | От англ. слова pop — извлекать, раскошеливаться |
Формат | POP операнд |
Действие | Извлекает значение из стека |
Примечание | Если нужно достать из стека все РОН, уложенные командой PUSHA, используйте POPA/POPAD |
Следующий пример (prax06.com).
Наберите в Hiew’e эти байты:
B8 CD 20 50-EB FD
Теперь посмотрите на асмовый вид:
Адреса Байты Имена Операнды 00000000: B8CD20 mov ax,020CD 00000003: 50 push ax 00000004: EBFD jmps 000000003
Давайте проанализируем этот пример.
00000000: B8CD20 mov ax,020CD
Эта строка помещает в регистр AX значение 20CD.
00000003: 50 push ax
Эта строка кладёт в стек word (слово) 20CDh из регистра EAX. Причём в памяти слово вы увидите как положено — CD,20h.
00000004: EBFD jmps 000000003
А вот эта строка зацикливает выполнение строки с адресом 03. То есть после выполнения строки 03 в строке 04 будет совершаться безусловный переход к строке 03.
Так почему же этот пример не вешает комп в ДОС и не виснет сам в WinXP? Запустите и вы увидите, что прога выполнится за доли секунды.
Можете попробовать разобрать хитрость в CV сами, но это займёт некоторое время :).
Честно скажу, что отладчик типа CodeView не очень подходит для разбора этого пустячка.
Дело в том, что простые DOS-отладчики используют стек программы в своих целях, что сбивает значение регистра ESP между шагами.
Здесь бы лучше всего запустить пример под SoftIce’ом. И тогда вы могли бы «ровно» увидеть,
как программа 32637 раз запишет в стек два байта CD20h. Последний раз байты будут вписаны вместо команды «jmp 03».
CD20h — это машинный код команды int 20h (вызов прерывания завершения программ, как вы уже знаете).
Стек в com-программе начинается на дне её сегмента (FFFE — последний адрес, кратный двум).
Получается, что стек всё растёт и растёт вверх и ничего его не остановит, кроме завершающего или ошибочного кода.
И вот он заполняет всё свободное место в сегменте, а затем начинает затирать данные (в этом примере их нет). Далее затирается код программы.
Такая ситуация называется ошибкой переполнения стека. Для DOS-программ данная ошибка — одна из самых распространённых.
В Win32-программах механизм выделения стека более развит (ОСь сама занимается этим вопросом, используя средства защищённого режима).
Однако ошибки переполнения стеков — всё ещё настоящий вызов для программистов, особенно в сфере безопасности.
Вы только что могли наблюдать, как из-за такой ошибки стек начинает выполняться.
Ведь изначально программа просто отправляла данные CD20h в стек. Но затем стек вырос (адрес в ESP уменьшился!) до значения, равного адресу команды JMP 0103 (в CodeView этого не видно!). И вот, после следующего выполнения команды push AX, происходит чудесное превращение данных в исполняемый код.
В CodeView тоже можно проследить самый интересный момент этого примера. Измените регистр указателя стека (ESP). Например, установите в нём значение 200h (чтоб долго не трейсить). После этого — F10 много раз, и прога завершится. Посмотрите ещё и ещё раз. Может быть, вы придумаете, как такой фокус можно использовать в своих целях ;).
Предлагаю вам ещё один пример Win32.
Учтите, что русские буквы должны быть в кодировке ANSI (стандарт для форточек).
prax07.asm:
.386 .model flat, stdcall option casemap :none ; case sensitive ;######################################################################### include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib ;######################################################################### .data MsgBoxCaption db "It's my first command line for Win32",0 MsgBoxText db "Аргументы командной строки отсутствуют",0 ;######################################################################### .code start: call GetCommandLine ; API. Возвращает в EAX адрес командной строки mov ECX,512d add ECX,EAX unquote: ; Метка цикла нахождения закрывающей кавычки inc EAX cmp EAX,ECX jz NO cmp byte ptr[EAX],22h jnz unquote Arg_search: ; Метка цикла нахождения аргумента командной строки inc EAX cmp byte ptr[EAX],0 jz NO cmp byte ptr[EAX],20h jz Arg_search push 0 push offset MsgBoxCaption push EAX push 0 call MessageBox ; Вызов API-функции вывода сообщения на экран push 0 ; Пустой параметр для функции выхода call ExitProcess ; Вызов API-функции выхода NO: push 0 ; Параметр "MB_OK" push offset MsgBoxCaption ; Параметр "адрес заголовка" push offset MsgBoxText ; Параметр "адрес текста сообщения" push 0 ; Параметр "родительское окно" call MessageBox ; Вызов API-функции вывода сообщения на экран push 0 ; Пустой параметр для функции выхода call ExitProcess ; Вызов API-функции выхода end start
Наберите, пожалуйста, сами секцию кода. Я уверен, что у вас будут опечатки, и это очень ускорит процесс обучения.
Для того, чтоб пример сделал то, что ему положено, запустите его с каким-нибудь ключом типа: prax07.exe qwerty |
Ну что ж, отладчик Olly — ваш лучший друг. При открытии впишите ключ в поле «Arguments». Не торопитесь, подумайте что к чему.
Здесь используются 3 API-функции:
- GetCommandLine — возвращает в регистр EAX адрес командной строки программы вместе с путём в кавычках. Например:
«D:\tut\prax07.exe » qwerty - MessageBox — вызывает функцию вывода сообщений на экран.
- ExitProcess — функция завершения.
Осталась только одна команда, которую вы пока не знаете в этой программе. Команда CALL, но это целая тема. И, между прочим, последняя теоретическая тема в наших уроках.
Bitfry
OFFSET Оператор на языке ассемблера для процессоров x86
Задать вопрос
спросил
Изменено 6 лет, 3 месяца назад
Просмотрено 10 тысяч раз
Меня несколько смущает понятие OFFSET Operator. Согласно книге Кипа Р. Ирвина «Язык ассемблера для процессоров x86», он определяет оператор смещения как оператор, возвращающий расстояние переменной от начала окружающего ее сегмента. Он также говорит, что оператор смещения возвращает смещение метки данных и представляет собой расстояние (в байтах) метки от начала сегмента данных. Что такое смещение? Что он имеет в виду под расстоянием метки от начала сегмента данных?
И пришел ли он к такому результату:
Он объявляет три разных типа переменных:
.data bVal БАЙТ ? wVal СЛОВО ? dVal DWORD ? dVal2 двойное слово?
Если бы значение bVal располагалось по смещению 00404000 (шестнадцатеричное), оператор OFFSET вернул бы следующие значения:
mov esi, OFFSET bVal ;ESI = 00404000h mov esi, OFFSET wVal ;ESI = 00404001h mov esi, OFFSET dVal ;ESI = 00404003h mov esi, OFFSET dVal2 ;ESI = 00404007h
Откуда он взял эти значения? Пожалуйста помоги. Большое спасибо!
- в сборе
- x86
- nasm
- masm
- irvine32
1
За исключением 16-битного кода, в обычных ОС виртуальная память является плоской, и все сегменты имеют base=0.
Так что это просто сложный способ сказать, что OFFSET var
дает вам адрес var
как непосредственный, вместо загрузки с него.
мов esi, bVal ; загрузить из [bVal], в синтаксисе MASM
mov esi, OFFSET bVal ; esi= адрес bVal мов эси, [эси] ; нагрузка от [bVal]
См. также раздел «Разница сборки между [var] и var», чтобы узнать о различиях между синтаксисом MASM и NASM.
Теория смещения означает: «Смещение — это количество адресов в базовом адресе для перехода к определенному абсолютному адресу». Таким образом, это выглядит как индекс (элемент данных или поле) массива. (элемент данных, блок или кадр). Смещение указывает расстояние между элементом данных и элементом данных. Все элементы элемента имеют одинаковый размер (обычно задается в байтах или словах).
Итак, в вашем случае «данные» — это сегмент памяти, блок или элемент, а переменные в этом сегменте — элементы данных или смещения. Эти смещения — виртуальный адрес пространства несмежных областей физической памяти. Итак, эти числа представляют виртуальный адрес пространства несмежных областей физической памяти.
Зарегистрируйтесь или войдите
Зарегистрироваться через Google
Зарегистрироваться через Facebook
Зарегистрируйтесь, используя адрес электронной почты и пароль
Опубликовать как гость
Электронная почта
Требуется, но не отображается
Опубликовать как гость
Электронная почта
Требуется, но не отображается
arm — Что такое смещения в сборке и как их использовать?
Задать вопрос
спросил
Изменено 4 года, 4 месяца назад
Просмотрено 6к раз
В приведенном ниже коде человек указывает в регистре адрес, я понял. Почему позже он просто не загрузил R3 в R1, почему нужно было сместить его на 0x14?
Как R1 переместил несколько строк кода и что заставило его это сделать? Это действительно сбивает меня с толку, и часы поиска не дали четкого ответа.
Код украден отсюда.
http://mbed.org/forum/mbed/topic/3205/
my_asm ; установить указатель на базовый адрес порта 1 LDR R1, = 0x2009c020 ; указатель на базу port1 LDR R3, = 0x00040000 ; установите контакт порта LED 1 на выход STR R3,[R1,#0x00] ; установить биты направления (base+0x00) петля LDR R3, = 0x00000000 ; выключить светодиод 1 (все биты равны нулю) СТР R3,[R1,#0x14] ; установить выходы напрямую (база + 0x14) LDR R3, = 0x00040000 ; включить светодиод 1 (бит 18, также известный как P1.18, равен «единице») СТР R3,[R1,#0x14] ; установить выходы напрямую (база + 0x14) петля В; переход к циклу (бесконечный цикл!) ВЫРОВНЯТЬ КОНЕЦ
- сборка
- рука
Смещения используются в ассемблере для доступа к структурам данных.
В коде, который вы используете, вы можете увидеть загружаемый базовый адрес . Это начальный адрес структуры данных.
Структура данных будет определена где-то, но не в коде. Определение часто представляет собой таблицу, показывающую, например,
- 4 байта для этого .
- 2 байта для этого
- 4 байта для чего-то другого
- и т. д.
Современные процессоры используют подобные карты памяти для ввода и вывода. Каждое устройство «сопоставляется» с определенным адресом. Это базовый адрес в вашем коде.
Не зная, какое оборудование подключено к вашему процессору, мы не можем сказать вам, какая в нем структура данных.
2
Причина в том, что это кратчайший (наименьшее количество инструкций) способ добиться желаемого эффекта. Делаются две вещи: (1) настройка порта для вывода и (2) переключение вывода. Поскольку ячейки памяти, в которые необходимо выполнить запись для выполнения этих операций, находятся рядом, для адресации обоих необходимо использовать возможности процессора по базовому/смещению.
Тот же эффект может быть достигнут с помощью следующего кода, но без пользы. Он больше (меньше вероятность того, что он поместится в фиксированной памяти), и цикл не быстрее (процессор все равно должен добавить ноль к новому значению в R1 при выполнении инструкций STR).
мой_асм ; установить указатель на базовый адрес порта 1 LDR R1, = 0x2009c020 ; указатель на базу port1 LDR R3, = 0x00040000 ; установите контакт порта LED 1 на выход STR R3,[R1,#0x00] ; установить биты направления (base+0x00) LDR R1, = 0x2009с034 ; указатель на port1 выводит <-- изменить R1 петля LDR R3, = 0x00000000 ; выключить светодиод 1 (все биты равны нулю) STR R3,[R1,#0x00] ; установить выходы <-- больше нет смещения LDR R3, = 0x00040000 ; включить светодиод 1 (бит 18, также известный как P1.18, равен «единице») STR R3,[R1,#0x00] ; установить выходы <-- больше нет смещения петля В; переход к циклу (бесконечный цикл!) ВЫРОВНЯТЬ КОНЕЦ
Регистр 1 загружается с базовым адресом для Порт 1 и третьей инструкцией. ..
STR R3, [R1, #0x00] ; установить биты направления (база + 0x00)
...предполагает, что второй байт данных, адресованных содержимым R1, является битами направления для светодиода на порту 1.
Теоретически вы, вероятно, могли бы загрузить R1 для адресации данных по смещению 0x014 после этой инструкции завершено, но это будет означать, что вы включили дополнительную и ненужную инструкцию. Как правило, если вы пишете что-то на ассемблере, вы пытаетесь сделать это максимально эффективно.
STR R3, [R1, #0x14] ; установить выходы напрямую (база + 0x14)
Что вы можете узнать из приведенного выше кода, так это то, что указатели поворота имеют смещение 1, а выходные данные находятся в байте 15 (, я думаю, ) в данных, относящихся к порту 1. Кроме этого, у нас нет никакой информации. .
Однако, если вам нужно получить доступ к каким-либо другим данным, относящимся к порту 1, позже в процессе, и вы обновили базовый регистр, указав на смещение 0x14 после начала записи для порта 1, вам придется загрузить адрес для этого или перезагрузите базу и получите смещение, особенно если оно было где-то между базой данных для порта 1 и смещением 14.