ПРОФЕССИОНАЛЬНЫЙ ПОДХОД
Отладчик машинного кода "TRACER"
Создание резидентных программ - один из самых интересных моментов в работе программиста. Резидентные программы работают как бы "поверх" основных и позволяют не выходя из главной программы выполнять еще какую-либо ценную задачу. Так, например, в разделе "ФОРУМ" Вам была представлена резидентная программа-русификатор, которая 50 раз в секунду проверяет нажатие комбинации клавиш SYM.SH. + ENTER и, если они не нажаты, то резидент себя не проявляет и остается "прозрачным" для пользователя.
В игровых программах резидентные процедуры как правило занимаются тем, что воспроизводят музыку одновременно с работой других процедур, а иногда им поручают обслуживание клавиатуры в поисках нажатия нужной клавиши. Нередко их используют и при организации мультипликации.
Создание резидентных программ для "Спектрума" основывается на использовании режима прерываний 2-го рода (IM2) и сегодня мы представляем Вашему вниманию еще одну полезную резидентную программу, которая поможет тем, кто программирует в машинных кодах, особенно там, где иные средства откажутся работать.
Кто не испытывал трудностей при отладке программ в машинных кодах, когда казалось бы вполне "хорошая" программа почему-то вдруг ни с того ни с сего "зависает" и никак не удается определить, почему же это происходит. Проблему отчасти решает пошаговая отладка программы при помощи мощных мониторов, таких, как, например "MONS-3", "MONS-4". Но только отчасти, так как для того, чтобы вручную "прощелкать" достаточно большую программу, требуется уйма времени, ведь программы содержат массу различных циклов и придется повторять выполнение программы в одних и тех же адресах многократно. В общем задача эта - не из простых. Пошаговая отладка - это конечно, хорошо, но неужели только этим ограничиваются возможности "влезть внутрь" программы и посмотреть, что же там происходит. Сегодня мы предлагаем читателям еще один инструмент, позволяющий заглянуть внутрь программы во время ее работы, причем в режиме реального времени. Конечно, использование приведенного метода требует определенной квалификации, но если Вы хорошо разберетесь с ним, то получите в свой арсенал инструмент, значение которого трудно переоценить.
Итак, вниманию читателей предлагается отладчик машинного кода, использующий режим прерываний второго рода.
Программа-отладчик машинного кода называется "TRACER". Она позволяет выводить в правой части экрана заданную информацию в шестнадцатеричном коде. Что это может быть за информация? Это зависит от Ваших требований. Что Вы зададите. Например, Вы хотите просмотреть число, записанное в какой-нибудь ячейке памяти, как оно изменяется. Или как меняется содержимое какого-то регистра. Или что записано на вершине машинного стека. Принцип работы отладчика состоит в том, что 50 раз в секунду будет происходить прерывание Вашей исследуемой программы и 50 раз в секунду интересующие Вас сведения будут выведены на экран в столбец в правой части экрана. Процедура, обслуживающая прерывание, должна формировать требуемый параметр и выдавать его на печать. От того, что конкретно Вы хотите просмотреть, зависит начало этой процедуры. В качестве примера, рассмотрим вариант кодов, который позволяет просмотреть число, записанное на вершине машинного стека. Блок кодов, который это делает, приведен в распечатке (Листинг 1), а возможный пример работы отладчика - на рис. 1.
Рис. 1
Включение отладчика в работу происходит при выполнении инициализирующей подпрограммы START, расположенной с адреса FEF7H (RANDOMIZE USR 65271). При этом происходит следующее. Вначале задается адрес в дисплейном файле, куда будет производиться вывод результатов: в регистр HL записывается адрес верхней пиксельной линии того знакоместа экрана, куда будет производиться печать. Это значение заносится в системную ячейку FF13H. Затем обнуляется другая системная ячейка 5C81H (это неиспользуемый адрес в таблице системных переменных). Здесь будет организован счетчик строк для выводимой информации. После этого происходит включение режима прерываний второго рода. В регистр I процессора записывается число FEH. Поэтому при прерывании произойдет переход на программу, адрес которой записан в ячейке памяти FEFFH. Там записан адрес FE5CH. Это начало обрабатывающей процедуры. Затем происходит переключение на режим прерываний второго рода и возврат.
Теперь 50 раз в секунду будет выполняться обслуживающая процедура. По условию, которое мы задали, она должна выдавать значение двухбайтного числа, находящегося на вершине машинного стека. Считывание этого значения происходит следующим образом. Текущее значение регистра HL сохраняется в неиспользуемой ячейке 6CB0H в таблице системных переменных. Далее интересующее нас число с вершины стека заносится в регистр HL. При этом необходимо восстановить стек в том виде, в каком он был, что выполняется при помощи обратной записи числа из HL на стек. Далее на стеке сохраняются значения всех регистров процессора, которые задействованы и будут изменены в процедуре обработки прерывания.
Теперь надо интересующее нас число (в регистре HL) вывести на печать. Это выполняется следующим образом. Проверяется значение счетчика строк 5C81H. Если не достигнут нижний край экрана, то происходит переход на процедуру CONT, а если достигнут, то для последующего вывода задается опять верхняя строка экрана путем занесения в системный счетчик FF13H исходного адреса в дисплейном файле. Счетчик строк при этом обнуляется.
Процедура CONT начинается с того, что в регистр DE наносится текущее значение адреса для вывода в дисплейном файле. Затем старший байт интересующего числа записывается в регистр A и при помощи ротации вправо старший полубайт становится на место младшего. Выполняется его печать при помощи подпрограммы PRINT, которая выполняет печать четырех младших битов, содержащихся в регистре A (в шестнадцатеричном представлении четырем битам соответствует один символ). После печати значение DE увеличивается на единицу, что соответствует переходу к следующему знакоместу. Затем в регистр A опять записывается значение из регистра H и выполняется печать младшего полубайта. Значение DE опять увеличивается на 1. Далее то же повторяется и со значением регистра L, точно так же за два приема выполняется печать двух полубайтов.
Далее, путем выполнения ротации и суммирования, в счетчике FF13H получается следующий адрес в дисплейном файле, соответствующий выводу на следующей строке. Такой способ вычисления этого адреса связан с тем, что экранная память состоит из трех сегментов и надо получить правильный результат при переходе от одного сегмента к
другому. В нашей книге "Элементарная графика" мы подробно разбирали все вопросы, связанные с выводом на экран. Поэтому, для более ясного представления о том, как все это происходит, заказывайте и читайте нашу книгу.
Листинг 1
FE5C |
E2B05C INT |
S/R |
LD |
(#5CBO),HL |
Сохранение HL. |
FE5F |
E1 |
|
POP |
HL |
Получение требуемого |
|
|
|
|
|
параметра в HL. |
FE60 |
E5 |
|
PUSH |
HL |
Восстановление стека. |
FE61 |
F5 |
|
PUSH |
AF |
Сохранение |
FE62 |
C5 |
|
PUSH |
BC |
значений |
FE63 |
D5 |
|
PUSH |
DE |
регистров. |
FE64 |
3A815C |
|
LD |
A,(#5C81) |
Переход к |
FE67 |
3C |
|
INC |
A |
следующей |
FE68 |
32815C |
|
LD |
(#5C81),A |
строке. |
FE6B |
FE16 |
|
CP |
#16 |
Проверка на достижение |
FE6D |
200B |
|
JR |
NZ,#FE7A |
конца экрана. |
FE6F |
111C40 |
|
LD |
DE,#401C |
Переход на |
FE72 |
ED5313FF |
|
LD |
(#FF13),DE |
начало экрана. |
FE76 |
AF |
|
XOR |
A |
Обнуление |
FE77 |
32815C |
|
LD |
(#5C81),A |
счетчика строк |
FE7A |
ED5B13FF |
CONT |
LD |
DE,(#FF13) |
Текущий адрес |
|
|
|
|
|
в дисплейном файле. |
FE7F |
7C |
|
LD |
A,H |
Выделение |
FE7F |
1F |
|
RRA |
|
старшего |
FE80 |
1F |
|
RRA |
|
полубайта. |
FE81 |
1F |
|
RRA |
|
|
FE82 |
1F |
|
RRA |
|
|
FE83 |
CDB9FE |
|
CALL |
#FEB9 |
Печать старшего |
FE86 |
13 |
|
INC |
DE |
полубайта. |
FE87 |
7C |
|
LD |
A,H |
|
FE88 |
CDB9FE |
|
CALL |
#FEB9 |
Печать младшего |
FE8B |
13 |
|
INC |
DE |
полубайта. |
FE8C |
7D |
|
LD |
A,L |
Выделение |
FE8D |
1F |
|
RRA |
|
старшего |
FE8E |
1F |
|
RRA |
|
полубайта. |
FE8F |
1F |
|
RRA |
|
|
FE90 |
1F |
|
RRA |
|
|
FE91 |
CDB9FE |
|
CALL |
#FEB9 |
Печать старшего |
FE94 |
13 |
|
INC |
DE |
полубайта. |
FE95 |
7D |
|
LD |
A,L |
|
FE96 |
CDB9FE |
|
CALL |
#FEB9 |
Печать мл. полубайта. |
FE99 |
2A13FF |
|
LD |
HL,(#FF13) |
Расчет |
FE9C |
CB1C |
|
RR |
H |
адреса |
FE9E |
CB1C |
|
RR |
H |
в дисплейном |
FEA0 |
CB1C |
|
RR |
H |
файле |
FEA2 |
012000 |
|
LD |
BC,#0020 |
с учетом |
FEA5 |
ED4A |
|
ADC |
HL,BC |
сегмента |
FEA7 |
CB14 |
|
RL |
H |
экрана. |
FEA9 |
CB14 |
|
RL |
H |
|
FEAB |
CB14 |
|
RL |
H |
|
FEAD |
2213FF |
|
LD |
(#FF13),HL |
|
FEB0 |
D1 |
|
POP |
DL |
Финишные операции |
FEB1 |
C1 |
|
POP |
BC |
по восстановлению |
FEB3 |
F1 |
|
POP |
AF |
значений |
FEB3 |
2AB05C |
|
LD |
HL,(#5CB0) |
регистров. |
FEB6 |
C338OO |
|
JP |
#0038 |
Переход на RST #38 |
FEB9 |
E60F |
PRINT |
AND |
#0F |
Преобразование |
FEBB |
87 |
|
ADD |
A,A |
кода в символ |
FEBC |
E5 |
|
PUSH |
ML |
согласно |
FEBD |
21D7FE |
|
LD |
HL,#FED7 |
таблице |
FEC0 |
0600 |
|
LD |
B,#00 |
с учетом |
FEC2 |
4F |
|
LD |
C,A |
шестнадцати |
FEC3 |
09 |
|
ADD |
HL,BC |
ричного |
FEC4 |
46 |
|
LD |
B,(HL) |
представления. |
FEC5 |
23 |
|
INC |
HL |
|
FEC6 |
4E |
|
LD |
C,(HL) |
|
FEC7 |
C5 |
|
PUSH |
BC |
|
FEC8 |
E1 |
|
POP |
HL |
|
FEC9 |
0608 |
|
LD |
B,#08 |
|
FECB |
7E |
PRT |
LD |
A, (HL) |
Вывод |
FECC |
12 |
|
LD |
(DE),A |
символа |
FECD |
23 |
|
INC |
HL |
на |
FECE |
14 |
|
INC |
D |
экран. |
FECF |
10FA |
|
DJNZ |
#FECB |
|
FED1 |
7A |
|
LD |
A,D |
|
FED2 |
D608 |
|
SUB |
#08 |
|
FED4 |
57 |
|
LD |
D,A |
|
FED5 |
E1 |
|
POP |
HL |
|
FEDS |
C9 |
|
RET |
|
|
FED7 |
3D803D |
TABLE |
DEFB |
|
Таблица |
FEDA |
883D90 |
|
DEFB |
|
для |
FEDD |
3D983D |
|
DEFB |
|
преобразования |
FEE0 |
A03DA8 |
|
DEFB |
|
кода |
FEE3 |
3DB03D |
|
DEFB |
|
в символ |
FEE6 |
B83DC0 |
|
DEFB |
|
с учетом |
FBE9 |
3DC83E |
|
DEFB |
|
шестнадцати- |
FEEC |
083E10 |
|
DEFB |
|
ричного |
FEEF |
3E183E |
|
DEFB |
|
представления. |
FEF2 |
203E28 |
|
DEFB |
|
|
FEF5 |
3E30 |
|
DEFB |
|
|
FEF7 |
211C40 |
START |
LD |
HL,#401C |
Инициализирующая |
FEFA |
2213FF |
|
LD |
(#FF13),HL |
процедура |
FEFD |
1802 |
|
JR |
#FF01 |
|
FEFF |
5CFE |
ADR |
DEFB |
|
Адрес перехода по IM2. |
FF01 |
AF |
PASS |
XOR |
A |
Продолжение |
FF02 |
32815C |
|
LD |
(#5C81),A |
инициализирующей |
FF05 |
3EFE |
|
LD |
A,#FE |
процедуры. |
FF07 |
ED47 |
|
LD |
I,A |
|
FF09 |
ED5E |
|
IM |
2 |
|
FF0B |
C9 |
|
RET |
|
|
FF0C |
ED56 |
STOP |
IM |
1 |
Восстанавливающая |
FF0E |
3E3F |
|
LD |
A,#3F |
процедура. |
FF10 |
ED47 |
|
LD |
I,A |
|
FF12 |
C9 |
|
RET |
|
|
FF13 |
00 |
SCRP |
NOP |
|
Текущее значение адреса |
FF14 |
00 |
|
NOP |
|
в дисплейном файле. |
После выполнения печати искомой величины, происходят финишные операции. Это восстановление со стека значений регистров процессора. При этом значение регистра HL, как мы помним, находится в ячейке 5CD0H, оттуда его и считываем. Далее - переход на адрес 0038H, как и в случае прерывания первого рода.
Непосредственно печать выполняется при помощи подпрограмм печати PRINT и PRT. Вначале зануляются старшие байты регистра A (выполняется печать только одного полубайта). Для того, чтобы обеспечить печать в шестнадцатеричном виде, используется перевод кода в символ при помощи таблицы, расположенной с адреса FED7H. В зависимости от значения кода происходит установка в регистре HL адреса в символьном наборе, с которого находится изображение этого символа. Затем процедура PRT выполняет
переписывание восьми байт из символьного набора в дисплейный файл.
В результате работы программы-отладчика в правой части экрана Вы будете видеть столбец непрерывно меняющихся цифр. Это и есть результат работы программы - числа, которые находятся на вершине стека в процессе работы программы. Они меняются достаточно быстро, но ведь все происходит в режиме реального времени. Да и к тому же это просто демонстрационный пример, показывающий возможности программы.
На практике, для того, чтобы "вытаскивать" из исследуемой программы требуемые параметры, Вам придется несколько видоизменить начальную часть процедуры, обслуживающей прерывание. Хорошенько разобравшись с принципом действия этой программы, Вы сможете легко применять ее для своих вполне конкретных целей.
Для того, чтобы отключить отладчик, надо выполнить останавливающую подпрограмму, расположенную с адреса FF0CH (RANDOMIZE USR 65292). При этом происходит переключение на режим прерываний 1 рода - восстановление обычного режима работы "Спектрума".
И в заключении об одном ограничении на работу отладчика. Вывод на экран будет возможен только в том случае, если прерывания разрешены (EI). Если в исследуемой программе они запрещаются командой DI, то прерываний не будет и, следовательно, не будет вывода на экран.