	    ОПЫТ ДИЗАССЕМБЛИРОВАНИЯ БОЛЬШОЙ .COM ПРОГРАММЫ




			      А.Б.КРУПНИК



     В данной статье мне хочется рассказать о дизассемблировании большой
программы (графического редактора). Не будучи знатоком ассемблера, не
зная до сих пор, как использовать большинство возможностей своего дизас-
семблера(DisDoc 2.3), я все же решился написать эту  статью, так как пре-
красно помню, в какой кромешной тьме начинал заниматься дизассемблирова-
нием.
     Тогда, год назад, я попробовал дизассемблировать простенькую програм-
му и был страшно удивлен  тем, что дизассемблер делает это неправильно, и
при повторном ассеблировании программа не работала так, как надо. Тогда же
мне удалось поговорить со знающим человеком и, хотя я  чувствовал себя на-
ивным дурачком, мне удалось выяснить главное: ПОЛНОЕ,АВТОМАТИЧЕСКОЕ ДИЗАС-
СЕМБЛИРОВАНИЕ НЕВОЗМОЖНО, над тем текстом, который выдает дизассемблер, нуж-
но довольно долго работать, прежде чем ассемблирование этого текста даст ра-
ботоспособную программу.
     В дальнейшем я постараюсь рассказать о тех приемах, которые превращают
"плохой" текст в "хороший" , т.е. в текст, который не только дает корректно
работающую программу при ассемблировании, но и позволяет себя изменить, что-
бы усовершенствовать исходную программу.



         		      ПОЧЕМУ DisDoc?

    SOURSER - это название знают все, кто хотя бы краем уха слышал о дизас-
себлировании. Считается, что это дизассеблер замечательный, мощный,  не име-
ющий конкурентов. Я думаю, что слухи об огромных преимуществах SOURSERа силь-
но преувеличены. У меня сложилось такое впечатление, что при дизассемблирова-
нии небольших программ (до 7 кб.) SOURSER предпочтительнее. Когда программа
велика (в моем случае - 58 кб ), SOURSER работает очень медленно и, на мой
взгляд, не дает никаких преимуществ.
    Выбор дизассемблера DisDoc 2.3 был для меня во многом случаен. Начиная ра-
боту, я получил тексты на ассемблере как с помощью SOURSERa (версия 3.07), так
и с помощью дизассемблера DisDoc 2.3. Затем оба текста после устранения очевид-
ных ошибок были ассемблированы. И вот, то, что было выдано SOURSERом, повисло
сразу, а то, что выдал DisDoc 2.3, прежде чем повиснуть, вывело на экран нес-
колько линий. Это и определило выбор. В процессе работы я не раз имел возмож-
ность оценить основное преимущество дизассемблера DisDoc - интуитивно понятный,
неизощренный, удобный и компактный листинг.
Чтобы понять дальнейшее, необходимо познакомиться с отрывком из листинга, кото-
рый выдает DisDoc 2.3

		mov	cx,WORD PTR ds:d02453	;02430
b02430:		add	cx,bx			;02434
		mov	bx,99e7h		;02436
		mov	dx,WORD PTR ds:d02449	;02439
		mov	al,BYTE PTR ds:d02446	;0243d
		call	s383 ;<09060>		;02440
		push	cs			;02443
		pop	ds			;02444
		ret				;02445
;-----------------------------------------------------
d02446		db	00			;02446 .
d02447		db	00,00			;02447 ..
d02449		db	00,00			;02449 ..

В поле комментариев указано смещение, которое имела данная инструкция в исход-
ной программе. Например, если вы в исходной программе, подвергаемой дизассемб-
лированию, посмотрите отладчиком смещение 02434, то там окажется инструкция
add  cx,bx - на это можно положиться! Очень хороши названия меток и элементов
данных. По ним сразу можно понять, какое смещение они имели в исходной програм-
ме. Например, метка b02430 имела смещение 02430, элемент данных d02446 имел
смещение 02446 и т.д. То же самое относится и к подпрограммам. После вызова
подпрограммы  в треугольных скобках указано смещение, которое имела эта подпро-
грамма в исходной программе. Например, подпрограмма s383 начиналась в исходной
программе со смещения 09060. Такая организация листинга позволяет сохранить
однозначное соответствие с исходной программой, что дает возможность проверить
отладчиком сомнительные куски кода и данных, сравнить текст, выданный дизассем-
блером с тем, что есть на самом деле. Это поистине драгоценная возможность.
    Нужно сказать, что DisDoc имеет большие недостатки, о которых речь еще пой-
дет, и, следовательно, применение того или иного дизассемблера - дело вкуса.
В любом случае обязательно встретятся


		         ФУНДАМЕНТАЛЬНЫЕ ПРОБЛЕМЫ


    1. Проблема OFFSETa


    Предположим, что в тексте, который выдал дизаccемблер есть такой фраг-
мент:


		mov	ax,bx			;1
		shl	ax,1 ;004bc		;2
		mov	si,8429h		;3
		add	si,ax			;4
		push	WORD PTR [si]		;5

Что засылается в регистр si в третьей строчке - число 8429h или смещение некой
метки? На этот  вопрос позволяет ответить пятая строчка, из которой видно, что
регистр si используется для косвенной адресации. Значит, исправленный фрагмент
должен выглядеть следующим образом:

		mov	ax,bx			;1
		shl	ax,1 ;004bc		;2
		mov	si,OFFSET d08429        ;3
		add	si,ax			;4
		push	WORD PTR [si]		;5

		................................

d08429          db      0ff,0ff,0f6	        ;8429
		db	0ff,0d8,0ff,0a6,0ff,60	;0842c .....`

Возможно, здесь у многих возникнет сомнение - нужно ли заменять число на со-
ответствующий OFFSET - ведь, казалось бы, в заново ассемблированной программе
данные будут иметь то же смещение? К сожалению, это не так. Во первых, мы,как
правило, не знаем, какой ассемблер применялся при транслировании оригинального
текста, а коды, полученные с помощью разных ассемблеров будут иметь разную дли-
ну, что приведет к изменению смещений. Например, команда AND  CX,0007h трансли-
руется MASMом 5.1 и TASMом 1.01  как  83E107  и занимает 3 байтa. Но эта же ко-
манда может быть транслирована как 81E10700 и занимать 4 байта. Во-вторых, даже
если смещение сохранится, программа не поддастся модификации, так как при
вставке какого-либо фрагмента кода изменятся смещения и все "развалится".
Итак, OFFSETы позволяют склеить программу, делают ее пригодной для модификации.
    Разобранный пример достаточно примитивен. Попробуем рассмотреть более слож-
ные ситуации и первым делом исследуем фрагмент текста, выданный дизассемблером:

		mov	bx,9006h		;08f66
b08f75:		mov	WORD PTR ds:d087d0,bx	;08f75
		.................................
		call	WORD PTR cs:d087d0	;08fc3
		......................................
;-----------------------------------------------------
		push	dx			;09006
		call	s419 ;<099a3>		;09007
		mov	al,BYTE PTR [si]	;0900a
		mov	BYTE PTR [si],0ffh	;0900c
		pop	dx			;0900f
		ret				;09010
;-----------------------------------------------------
Здесь возникает тот-же вопрос - что такое 9006h в первой строчке фрагмента -
смещение или просто число? Ответить на этот вопрос помогает информация, по-
мещенная дизассемблером в поле комментариев. Мы уже говорили о том что числа,
помещенные в этом поле, представляют собой смещения, которые имела инструкция
в исходной программе, подвергаемой дизассемблированию. Нетрудно догадаться, что
в приведенном фрагменте осуществляется косвенный вызов подпрограммы, и, следо-
вательно, 9006h - это смещение, а не число. Фрагмент должен  быть исправлен
так:

		mov	bx,OFFSET d09006        ;08f66
		......................................
;-----------------------------------------------------
d09006:		push	dx			;09006
		......................................
		ret				;09010

    Рассмотрим еще один пример косвенного вызова подпрограммы,  в котором
OFFSET попадает в область данных.

s390    	proc near
..........................................................
		mov	ax,WORD PTR [bx+8792h]	;092c7
		mov	WORD PTR ds:d087d2,ax	;092cb
...........................................................
		call	WORD PTR cs:d087d2	;093c8
		ret				;093d4
;-----------------------------------------------------
		ror	ah,1 ;093d5		;LO]-->[HI..LO]-->[HI
		jb	b093da ;093d7		;Jump if <  (no sign)
		ret				;093d9
b093da:		inc	si			;093da
		ret				;093db
............................................................

    Чтобы выяснить, что представляет собой 8792h, нужно посмотреть в область
со смещениями, близкими к этому числу. Приведем соответствующий фрагмент, вы-
данный дизассемблером:

d08790		db	00,00,0d5,93    	;08790 ......
.............................................................

Видно, что смещению 08792 соответствует слово 0d5,93. Теперь остается заметить,
что со смещения 093d5 в исходной программе начинается фрагмент повисшего кода

		ror	ah,1 ;093d5 !!!!!!	;LO]-->[HI..LO]-->[HI
		jb	b093da ;093d7		;Jump if <  (no sign)
		ret				;093d9
b093da:		inc	si			;093da
		ret				;093db

Следовательно,весь разобранный пример - это хитроумный косвенный вызов под-
программы. Исправленный фрагмент должен выглядеть так:

s390    	proc near
..........................................................
		mov	ax,WORD PTR [bx+OFFSET d08792]	;092c7
		mov	WORD PTR ds:d087d2,ax	;092cb
...........................................................
		call	WORD PTR cs:d087d2	;093c8
		ret				;093d4
;-----------------------------------------------------
d093d5:		ror	ah,1 ;093d5		;LO]-->[HI..LO]-->[HI
		jb	b093da ;093d7		;Jump if <  (no sign)
		ret				;093d9
b093da:		inc	si			;093da
		ret				;093db
............................................................

d08790		db	00,00    	;08790 ......
d08792          dw      OFFSET d093d5   ;08792

Здесь я предвижу большие возражения. Мне скажут, что все это можно интер-
претировать иначе, что мои доказательства неубедительны и т.д. С этим я со-
вершенно согласен. Более того, эти доказательства неубедительны и для меня.
Гораздо сильнее убеждает то, что программа после ассемблирования работает!
Дизассемблирование, как и отладка программ - процесс интуитивный. Опытный
человек испытывает особое удовольствие от того, что его немотивированные
догадки впоследствии подтверждаются. Как часто мысль, пришедшая в автобусе,
во сне, в компании, в самой неподходящей обстановке -  оказывается верной!
    Завершим этот пункт еще одним достаточно хитрым примером. В тексте, кото-
рый выдал дизассемблер, встретился такой фрагмент:

		mov	bx,4f71h 		;0522b
b0522e:		pop	ax			;0522e
		cmp	ax,bx			;0522f
		jnz	b0522e ;05231		;Jump not equal(ZF=0)
		mov	BYTE PTR ds:d041f4,00	;05233
		push	ax			;05238
		ret				;05239

		.................................
		call	s229 ;<04fc4>		;04f71

Возникает все тот же вопрос - что такое 4f71h - число или смещение? Чтобы
ответить на этот  вопрос, нужно понять, что делает этот участок программы.
Давайте попробуем в этом разобраться. Очевидно, из стека выталкивается
число, сравнивается с 4f71h и если нет равенства, выталкивается следующее
число. Если число равно 4f71h, то оно снова заталкивается в стек и происхо-
дит возврат из подпрограммы. Но куда? Ясно, что в то место, смещение кото-
рого было в исходной программе равно 4f71h. Как видно из текста, в этом мес-
те стоял вызов подпрограммы s229. Значит, таким странным образом вызывается
подпрограмма и 4f71h - это смещение! Исправленный фрагмент должен выглядеть
так:

		mov	bx, OFFSET d04f71       ;0522b
b0522e:		pop	ax			;0522e
		cmp	ax,bx			;0522f
		jnz	b0522e ;05231		;Jump not equal(ZF=0)
		mov	BYTE PTR ds:d041f4,00	;05233
		push	ax			;05238
		ret				;05239

		.................................
d04f71:		call	s229 ;<04fc4>		;04f71


    2.Как отличить данные от команд?

    Любой дизассемблер путает данные и команды. Особенно это относится к
.COM программам, где все перемешано. Рассмотрим простой пример:

		pop	cx			;03e56
		ret				;03e57
;-----------------------------------------------------
		add	BYTE PTR [bx+si],al	;03e58
		add	BYTE PTR [bx+si],al	;03e5a
m03e5c:		mov	BYTE PTR ds:d05830,01	;03e5c


В этом фрагменте встретились две вычурных, повисших инструкции:

		add	BYTE PTR [bx+si],al	;03e58
		add	BYTE PTR [bx+si],al	;03e5a

Сверху они ограничены инструкцией возврата из подпрограммы ret, а снизу - мет-
кой m03e5c. Ясно, что эти инструкции могут быть только данными. После передел-
ки приведенный фрагмент должен  выглядеть так:

		pop	cx			;03e56
		ret				;03e57
;-----------------------------------------------------
d03e58		dw      0                	;03e58
d03e5a 		db      0                	;03e5a
d03e5b          db      0
m03e5c:		mov	BYTE PTR ds:d05830,01	;03e5c

Тут возникает еще один вопрос: почему в одном случае стоит  dw, а в другом -
db? Ответ содержится в тексте, который выдал дизассемблер. Там можно найти
такие инструкции:

	     	mov	si,WORD PTR ds:d03e58	;03dd0
		mov	bl,BYTE PTR ds:d03e5a	;03dd4,

откуда следует, что  d03e58 рассматривается как слово, а d03e5a - как байт.
    Рассмотрим чуть более сложный, но, тем не менее, очень характерный пример.

b03f53:		cmp	al,05			;03f53
		jnz	b03f6b ;03f55		;Jump not equal(ZF=0)
 .....................................................
		ret				;03f69
;-----------------------------------------------------
		add	BYTE PTR [si],bh	;03f6a
		push	es			;03f6c
		jnz	b03f79 ;03f6d		;Jump not equal(ZF=0)


В приведенном фрагменте текста метка b03f6b отсутствует. Между тем эта метка
должна "разрубить" пополам инструкцию  add  BYTE PTR [si],bh , которая начи-
нается в оригинальной программе, подвергаемой дизассемблированию, со смещения
03f6a. Выход здесь может быть только один - смещению 03f6a соответствует байт
данных, а инструкция начинается со смещения 03f6b. Исправленный фрагмент дол-
жен выглядеть так:

b03f53:		cmp	al,05			;03f53
		jnz	b03f6b ;03f55		;Jump not equal(ZF=0)
......................................................
		ret				;03f69
;-----------------------------------------------------
d03f6a		db      0               	;03f6a
b03f6b:		cmp     al,06h			;03f6b
		jnz	b03f79 ;03f6d		;Jump not equal(ZF=0)

Путаница между данными и инструкциями возникает довольно часто. SOURSER спосо-
бен выдавать целые метры бессмысленных инструкций. DisDoc 2.3 в этом отношении
ведет себя лучше.


    3. Зависимость от транслятора

Программисты на ассемблере склонны пренебрегать правилами хорошего тона, на-
рушать все мыслимые табу, и это создает дополнительные трудности при  дизас-
семблировании. В качестве примера приведем фрагмент кода, выданного дизассем-
блером

s25     	proc near
		inc	cx			;0086b
		add	di,bp			;0086c
		adc	si,00			;0086e
		add	dx,si			;00871
		push	di			;00873
		shl	di,1 ;00874		;Multiply by 2's
		adc	dx,00			;00876
		pop	di			;00879
		ret				;0087a

Этот фрагмент представляется совершенно невинным, и  действительно, он дизас-
семблирован правильно. Вся беда в том, что программист задумал изменять этот
фрагмент, то есть резать по живому. Оказывается, в программе есть еще такой
кусок

		mov	di,086bh		;007f8
		......................................
		mov	BYTE PTR [di],4ah	;00800
		mov	BYTE PTR [di+07],0f1h	;00803
		mov	BYTE PTR [di+0ch],0d1h	;00807
		......................................
		ret				;00815

		              Рис.1



Так как di используется для косвенной адресации, нам прежде всего необходимо
заменить 086bh на соответствующий OFFSET  d0086b  и пометить этой меткой нача-
ло подпрограммы s25:

s25     	proc near
d0086b:		inc	cx			;0086b
     ..................................................

Далее следует понять, что делают инструкции, приведенные на рис.1 с подпрог-
раммой s25. Пусть эта подпрограмма асслемблирована с помощью TASM 1.01. Вы-
данный ассемблером код будет таким, как показано на рисунке 2.
  41             INC    CX               41             INC    CX
  03FD           ADD    DI,BP            01EF           ADD    DI,BP
  83D600         ADC    SI,0000          83D600         ADC    SI,0000
  03D6           ADD    DX,SI            01F2           ADD    DX,SI
  57             PUSH   DI               57             PUSH   DI
  D1E7           SHL    DI,1             D1E7           SHL    DI,1
  83D200         ADC    DX,0000          83D2000        ADC    DX,0000
  5F             POP    DI               5F             POP    DI
  C3             RET                     C3             RET

         Рис.2                                    Рис.3

Но вся беда в том, что исходная программа была ассемблирована другим ассемб-
лером и имеет вид, показанный на рисунке 3. Как видно из сравнения рисунков
2 и 3, TASM 1.01 и неизвестный ассемблер транслируют инструкции ADD по-разно-
му, и это приводит к катастрофическим последствиям. Действительно, посмотрим,
как воздействует участок кода, показанный на Рис.1 (перед этим заменим 086bh
на OFFSET d0086b) на подпрограмму s25, транслируемую TASMом (рис.4) и неиз-
вестным ассемблером (рис.5).

  4A             DEC    DX               4A             DEC    DX
  03FD           ADD    DI,BP            01EF           ADD    DI,BP
  83D600         ADC    SI,0000          83D600         ADC    SI,0000
  03F1           ADD    SI,CX   ;!!!!    01F1           ADD    CX,SI   ;!!!!
  57             PUSH   DI               57             PUSH   DI
  D1E7           SHL    DI,1             D1E7           SHL    DI,1
  83D100         ADC    CX,0000          83D100         ADC    CX,0000
  5F             POP    DI               5F             POP    DI
  C3             RET                     C3             RET

         Рис.4                                    Рис.5

Сравнение рисунков 4 и 5 показывает, что логика работы программы меняется в за-
висимости от того, какой ассемблер применялся.
   Как выкрутиться из этой ситуации, если нужного ассемблера нет под рукой? Са-
мый простой, но не очень красивый путь - поставить "заплатку". Чтобы можно было
использовать TASM, подпрогроамма s25 должна выглядеть так:

s25     	proc near
d0086b:		inc	cx			;0086b
		add     di,bp                   ;0086c
		adc	si,00			;0086e
		db      01,0f2                  ;add	dx,si !!!!!! ;00871
		push	di			;00873
		shl	di,1 ;00874		;Multiply by 2's
		adc	dx,00			;00876
		pop	di			;00879
		ret				;0087a



	        ОСОБЕННОСТИ И ОШИБКИ ДИЗАССЕМБЛЕРА DisDoc 2.3



    К сожалению, DisDoc 2.3 совершает ошибки, иногда регулярные, а иногда ред-
кие, коварные и даже подлые. Самая противная ошибка - случайный пропуск данных-
встречается довольно редко. Начнем с того, что встречается очень часто.

    1. EQU - кто тебя выдумал?

    В коде, выданном дизассемблером, часто попадаются такие загадочные куски:

;<00465>
s12     	proc near
d0046c		equ	00046ch
		cmp	bx,5ah			;00465

Каков  смысл присвоения  d0046c	 equ  00046ch ? Чтобы выяснить это, нужно
отыскать d0046c в тексте. В нашем случае элемент данных d0046c встречается
очень далеко от  своего первого появления - в подпрограмме s321

		mov	ax,0040h		;06257
;<es = 0040>
		mov	es,ax			;0625a
		mov	al,BYTE PTR es:d0046c	;0625c
		sti	 ;06260			;Turn ON Interrupts
b06261:		cmp	al,BYTE PTR es:d0046c	;06261
		jz	b06261 ;06266		;Jump if equal (ZF=1)
		mov	al,BYTE PTR es:d0046c	;06268
		dec	cx			;0626c
		jnz	b06261 ;0626d		;Jump not equal(ZF=0)
		pop	ax			;0626f
		out	61h,al ;06270		;060-067:8024 keybrd contrlr
;<es = 0000>
		pop	es			;06272
		ret				;06273
s321    	endp

			    Рис.6


При виде этого текста возникает догадка, что здесь идет зваимодействие с
областью данных BIOSa . Действительно, в регистр es засылается число 40,
т.е. es будет указывать на адрес 400 - начало этой области. Тогда следующий
вопрос - каков смысл адреса 046сh? Легко выяснить, что по этому адресу нахо-
дится счетчик прерываний от таймера. Если это так, то фрагмент, приведен-
ный на рис.6, обретает смысл - он дает задержку на число прерываний от тайме-
ра, заданное в регистре cx. Но если все сказанное верно, то d0046c должно быть
равно не 46сh, а просто 6сh! И действительно, если посмотреть подпрограмму s321
отладчиком, то станет ясно, что вместо  mov  al,BYTE PTR es:d0046c  в тексте
должно стоять  mov  al,6ch.
Итак, чтобы исправить эту ошибку, необходимо:
1. Удалить из начала подпрограммы s12 присвоение  d0046c equ	00046ch
2. Переписать приведенный на рис.6 фрагмент s321 следующим образом:

		mov	ax,0040h		;06257
;<es = 0040>
		mov	es,ax			;0625a
		mov	al,BYTE PTR es:006ch	;0625c
		sti	 ;06260			;Turn ON Interrupts
b06261:		cmp	al,BYTE PTR es:006ch	;06261
		jz	b06261 ;06266		;Jump if equal (ZF=1)
		mov	al,BYTE PTR es:006ch	;06268
		dec	cx			;0626c
		jnz	b06261 ;0626d		;Jump not equal(ZF=0)
		pop	ax			;0626f
		out	61h,al ;06270		;060-067:8024 keybrd contrlr
;<es = 0000>
		pop	es			;06272
		ret				;06273
s321    	endp

     Рассмотрим второй пример. В коде, выданном дизассемблером, встретился
такой кусок:

;<0074e>
s22     	proc near
d0076a		equ	00076ah
d00771		equ	000771h
		call	s24 ;<00791>		;0074e
		...............
b0076a:		push	cx			;0076a
		call	s25 ;<0086b>		;0076b
		call	s23 ;<00776>		;0076e
		pop	cx			;00771
		dec	bx			;00772

Поиск элемента данных d0076a окончился неудачей. А d00771 встретился в таком
фрагменте:
		.....................................
		mov	BYTE PTR ds:b0076a,51h	;0080b
		mov	BYTE PTR ds:d00771,59h	;00810
		......................................
Здесь явно идет модификация кода подпрограммы s22.  Значит, необходимо заменить
d00771 на b00771, пометить этой меткой соответствующую инструкцию в s22 и уда-
лить присвоения
              d0076a  equ	00076ah
              d00771  equ	000771h

Исправленный фрагмент s22 будет выглядеть так:


;<0074e>
s22     	proc near
		call	s24 ;<00791>		;0074e
......................................................
b0076a:		push	cx			;0076a
		call	s25 ;<0086b>		;0076b
		call	s23 ;<00776>		;0076e
b00771:		pop	cx			;00771
		dec	bx			;00772
      ..............................................
		mov	BYTE PTR ds:b0076a,51h	;0080b
		mov	BYTE PTR ds:b00771,59h	;00810
      ................................................


    Рассмотрим еще один пример. В начале s32 встретились уже знакомые псевдо-
операторы:

;<00bf7>
s32     	proc near
d00c1c		equ	000c1ch
d00c1e		equ	000c1eh

Если посмотреть в область со смещениями, близкими к с1с, то там окажется кусок
повисшего кода, который может быть только данными:

	       .......................................
		or	al,BYTE PTR [bp+di]	;00c14
		add	WORD PTR [bx+di],ax	;00c16
		add	BYTE PTR [bx+si],al	;00c18
		add	BYTE PTR [bx+si],al	;00c1a
		mov	di,1306h		;00c1c
		add	ax,06c0h		;00c1f
		......................................

			      Рис.7

Теперь нужно поискать идентификаторы d00c1c и d00c1e в тексте, выданном диз-
ассемблером. Очень быстро можно найти фрагменты типа:
          mov WORD PTR ds:d00c1c,ax,  mov WORD PTR ds:d00c1e,ax.
Значит, ошибка дизассемблера состоит в том, что он перепутал данные и команды
и на этой почве сделал два неправильных присваивания, equ, попавших в начало
подпрограммы s32.
Исправления будут заключаться в следующем:
1. Убрать из начала подпрограммы s32 два псевдооператора equ.
2. Переписать коды на  рисунке 7 следующим образом:

d00c14		db      0a,03,01,01,00,00,00,00         ;00c14
d00c1c		db      0bf,06                          ;00c1c
d00c1e          db      13,05,0c0,06                    ;00c1e

    В заключение рассмотрим совсем простенький фрагмент кода:

;<01252>
s39     	proc near
d0125d		equ	00125dh
d0125f		equ	00125fh
		dec	bh			;01252
		jz	b0124f ;01254		;Jump if equal (ZF=1)
		xor	ah,ah			;01256
		shl	al,1 ;01258		;Multiply by 2's
		rcl	ah,1 ;0125a		;CF<--[HI .. LO]<--CF
		ret				;0125c
;-----------------------------------------------------
		add	BYTE PTR [bx+si],al	;0125d
		add	BYTE PTR [bx+si],al	;0125f
s39     	endp

Укажем без комментариев, что подпрогромма s39 должна выглядеть так:

;<01252>
s39     	proc near
		dec	bh			;01252
		jz	b0124f ;01254		;Jump if equal (ZF=1)
		xor	ah,ah			;01256
		shl	al,1 ;01258		;Multiply by 2's
		rcl	ah,1 ;0125a		;CF<--[HI .. LO]<--CF
		ret				;0125c
;-----------------------------------------------------
d0125d		db      00,00            	;0125d
d0125f		db      00,00             	;0125f
s39     	endp


    В заключение этого пункта подведем итоги. Значки equ называют всевдоопе-
раторами. Если говорить о дизассемблере DisDoc 2.3, то это название удивительно
точное. Если в тексте встретится equ - то ошибка рядом. Между тем, иногда Dis-
Doc 2.3 употребляет equ вполне корректно. Так что будьте бдительны и не дайте
себя обмануть.



    2. Дурные ошибки.

    Иногда поведение дизассемблера трудно объяснить. Например, он выдает

		add	WORD PTR ds:d96be3,07	;038b6
		shr	WORD PTR ds:d96be3,cl ;038bb ;Divide by 2's
вместо
		add	WORD PTR ds:d06bf3,07	;038b6
		shr	WORD PTR ds:d06bf3,cl   ;038bb ;Divide by 2's ,
теряет или искажает куски данных. К счастью, это происходит очень редко.


СВЕДЕНИЯ  ОБ АВТОРЕ.
Крупник Александр Борисович, ст.научный сотрудник Нижегородского института
гигиены труда и профзаболеваний.
Домашний  адрес:
Российская Федерация ,603074, Н.Новгород,ул.Народная,38-443.
Тел. 36-35-93(р).
