Просмотр полной версии : Определение текущего адреса
Alex Rider
16.06.2012, 12:10
Всем привет!
Есть у меня такая задача: надо определить текущий исполняемый адрес (что-то типа LD HL,PC). Но не все так просто - мне заранее неизвестен контекст, в котором исполняется мой код, а именно:
на каком такте кадра вызван код;
разрешены ли прерывания;
что загружено в какие адреса RAM, какая страница ROM включена и что в ней находится;
способ вызова моего кода (отметается возможность достать из стека адрес возврата и почитать адрес вызова).
Классический способ (call #007c: dec sp: dec sp: pop hl) несовместим со всеми указанными проблемами. Чуть более правильный код di: call #007c: dec sp: dec sp: pop hl :ei решает только первую проблему. Определение режима препываний и разрешение их (если были разрешены) решает еще и вторую проблему. А вот решение третьей проблемы мне не ведомо - по адресу #007c ret быть не обязан, как и по любому другому адресу.
Видится 2 не самых удобных способа решения проблемы:
обязать вызывающий код передавать в одном из регистров адрес вызова (или заносить его в некоторую ячейку в теле вызываемого кода);
обязать вызывающий код делать дополнительную инициализацию при включенном ROM BASIC 48.
Оба способа мне не нравятся. Знает ли кто-нибудь способ определения текущего адреса в любых условиях без дополнительных ограничений для вызывающего кода?
ld hl,$
Лишен всех недостатков:)
NEO SPECTRUMAN
16.06.2012, 13:56
ld hl,$И что это дает если код перемещаемый, адрес этой команды на момент компиляции.
способ вызова моего кода (отметается возможность достать из стека адрес возврата и почитать адрес вызова).То есть проблема в том что при прерывании между командами 007c ret и pop hl похерется адрес возврата от call 007c?
Я бы сделал как нибудь так
call ldhlpc
....
ldhlpc
ld (p1+1),SP
p1 ld hl,(0000)
ret
И что это дает если код перемещаемый, адрес этой команды на момент компиляции.
То есть проблема в том что при прерывании между командами 007c ret и pop hl похерется адрес возврата от call 007c?
Я бы сделал как нибудь так
call ldhlpc
....
ldhlpc
ld (p1+1),SP
p1 ld hl,(0000)
ret
лол. call в абсолютный адрес? и зачем тогда определять что на стеке? call ldhlpc = ld hl,$ в данном случае.
call в абсолютный адрес? и зачем тогда определять что на стеке? call ldhlpc = ld hl,$ в данном случае.
Ваистену. Может сначала с требованиями определиться? А то перемещаемый код крайне плохо сочетается с абсолютными адресами (это топикстартеру).
NEO SPECTRUMAN
16.06.2012, 14:55
лол. call в абсолютный адрес? и зачем тогда определять что на стеке? call ldhlpc = ld hl,$ в данном случае.
Во 1-х это просто пример
Во 2-х Процедуру ldhlpc по постоянному адресу. И ябы держал её вне окна переключения страниц. А call ldhlpc среди перемещаемого кода.
dhlpc = ld hl,$ в данном случае
и что будет указывать ваш ld hl,$ если я в ходе работы програмы перемещу его в другой адрес.
и зачем тогда определять что на стеке
Чтоб определить с какого адреса была вызвана ldhlpc
NEO SPECTRUMAN,
call ldhlpc
...
ldhlpc
pop hl
jp (hl)
чо за огород вы тут развели
NEO SPECTRUMAN
16.06.2012, 15:05
чо за огород вы тут развели
Пытался без использования Push и Pop.
Об таком даже не подумал.
А главное на столько быстрей.:v2_thumb:
Alex Rider
16.06.2012, 16:34
ld hl,$
А то перемещаемый код крайне плохо сочетается с абсолютными адресами (это топикстартеру).
Мой косяк, однако. Не сказал, что код перемещаемый. Вообще говоря, он поставляется уже скомпиленный вместе с нужными данными другим софтописателям. Надо найти адрес начала данных.
call ldhlpc
Не-а. Этот код может быть запущен командой JP (HL), например.
начал читать, тоже подумал про pop hl:jp (hl), но только чтобы самому куда-то эти 2 команды положить:
ld hl,#xxxx ; opcodes of pop+jp
ld (#4000),hl
call #4000
; hl=$
; тут можно еще и содержимое #4000 восстановить
А вот решение третьей проблемы мне не ведомо - по адресу #007c ret быть не обязан, как и по любому другому адресу.
Если сначала сохранить значение с 4000h потом туда положить код 'RET' вызвать, восстановить? Да. Если код пересекается с 4000h, то не получится. Но это уже какой то вариант.
---------- Post added at 20:01 ---------- Previous post was at 20:00 ----------
Знает ли кто-нибудь способ определения текущего адреса в любых условиях без дополнительных ограничений для вызывающего кода?
Без ограничений имхо никак. Придётся чем-то жертвовать.
Если программа релоцируемая и загружена по произвольному адресу
то ее ктото загрузил
а если загрузил то пусть адрес и передает
если передать не хочет
то значит уже загрузил и настроил
значит можно шарится уже внутри программы
---------- Post added at 18:15 ---------- Previous post was at 18:14 ----------
а вот эти вот
"я что то откудато загрузил но вот как загрузил совершенно не понимаю
и где оно работает тоже не знаю и знать не хочу но сделайте мне хорошо"
нам тут не нужны
ее ктото загрузил
А если её кто-то загрузил, то он и может программу настроить на адрес загрузки. Программа должна иметь определенной формат с таблицей для пересчёта адресов. Все эти технологии давным давно придуманы и реализованы.
Если код пересекается с 4000h, то не получится.
даже если саму программу положили по #4000, то все получится:) (если #4000 самый первый адрес озу; можно так же юзать самый последний, лишь бы не попортить стек/таблицу прерываний (но что они делают по этим адресам?))
даже если саму программу положили по #4000
Да, получится, но не получится, если программа будет чуть выше. Мне считать было лень. Можно высчитать адрес, когда точно не получится. Последний байт - то же идея правильная. А стек там может быть. Когда делаем ld sp,0. при первом push или прерывании мы заюзаем последние 2 ячейки ОЗУ. FFFF и FFFE.
---------- Post added at 23:02 ---------- Previous post was at 22:54 ----------
Кстати, возможен и такой вариант:
ei
halt
di
dec sp
dec sp
pop hl
получаем адрес команды di
Alex Rider
16.06.2012, 21:55
а вот эти вот
я что то откудато загрузил но вот как загрузил совершенно не понимаю
и где оно работает тоже не знаю и знать не хочу но сделайте мне хорошо
нам тут не нужны
:) Это для новичков же все. Это про грабер мой. Придется писать инструкции и отвечать на лишние вопросы потом, поскольку планируется, что мой скомпиленный код будет тупо инкбиниться и зваться с параметраями по адресу, куда заинкбинили. При этом про координаты и номер спрайта автор вызывающего кода будет помнить автоматически, а пот адрес, который зовет - ... не знаю... Но выхода, похоже, нет.
По поводу #4000 - работа с графикой идет, не дело точками мелькать на экране.
По поводу #FFFx - там часто живет ISR, да и вообще кто его знает что там у автора. Хотя, надо подумать.
Так что остается только разочаровывающие меня варианты - либо преинициализация, либо передача адреса при вызове.
---------- Post added at 21:55 ---------- Previous post was at 21:47 ----------
Подумал. Этот вариант:
начал читать, тоже подумал про pop hl:jp (hl), но только чтобы самому куда-то эти 2 команды положить:
ld hl,#xxxx ; opcodes of pop+jp
ld (#4000),hl
call #4000
; hl=$
; тут можно еще и содержимое #4000 восстановить
плюс этот вариант:
мы заюзаем последние 2 ячейки ОЗУ. FFFF и FFFE.
Должны сработать, если запретить прерывания на время вызова. Проверю как руки дойдут.
Всем спасибо!
Но выхода, похоже, нет.
По поводу #4000 - работа с графикой идет, не дело точками мелькать на экране.
архитектура большенства процов включает СТЕК! суть которого "заранее выделенный сегмент памяти для временного хранения переменных (рабочая область)" так вот на стеке код создавай а не в экране
так вот на стеке код создавай а не в экране
код в студию! :)
bigral, да не вопрос
call (sp) и никаких проблем :)
А может через калькулятор ПЗУ?
Как-нибудь так:
RST #28 ; вызов калькулятора
DEFB #38 ; команда end calc
LD DE,7
DEC SP
DEC SP
POP HL
ADD HL,DE ; в HL адрес следующей команды (NOP)
NOP
Вроде-бы в любом ПЗУ должен быть обработчик калькулятора (иначе комп уже не совместим со спековыми программами.)
Destr, проблема в том что неизвестно есть ли вообще пзу
Destr, проблема в том что неизвестно есть ли вообще пзу
не, проблема в том, что прерывания разрешены, нельзя делать DEC SP, можно ошибиться.
Destr, проблема в том что неизвестно есть ли вообще пзу
Так вроде если ПЗУ нету, то и прогу запустить невозможно.
Загрузчик-то по любому на бэйсике, а бэйсик живёт в пзу и юзает калькулятор.
Или прога для чего-то экслюзивного пишется (не для спека как такового, а просто под z80) ?
не, проблема в том, что прерывания разрешены, нельзя делать DEC SP, можно ошибиться.
Ну, можно делать ADD HL,SP, к примеру. :)
не, проблема в том, что прерывания разрешены, нельзя делать DEC SP, можно ошибиться.
Запретить, а потом разрешить.
Есть ещё автоопределялка разрешены-ли прерывания.
Можно сперва определить, запретить, выяснить где находится PC, а потом разрешить прерывания (если на входе были разрешены).
Destr, зачем?
если можно
pop hl
jp (hl)
Destr, зачем?
если можно
pop hl
jp (hl)
Ну это если CALL делать.
Но как я понял это неподходящий вариант?
Любые абсолютные адреса - это неподходящий вариант)
Любые абсолютные адреса - это неподходящий вариант)
Ну если и RST #28 не устраивает, то остаётся только HALT.
Но при этом вектор прерываний всё-таки должен быть выставлен (или-же присутствовать ПЗУ, если IM 1 юзать)
Alex Rider
17.06.2012, 19:48
Ну если и RST #28 не устраивает, то остаётся только HALT.
Если код работает в неизвестном контексте, то:
ни на какие калькуляторы в ROM закладываться нельзя - после запуска проги ROM могли уже отключить; кроме того, калькулятор портит память;
про прерывания и их обработчики ничего не известно, поэтому EI и HALT делать нельзя.
Пока в качестве рабочего рассматривается такой вариант:
запоминаем состояние прерываний;
запрещаем прерывания;
запоминаем ячейки #FFFE и #FFFF;
заносим в #FFFE и #FFFF коды команд pop hl : jp (hl).
вызываем #FFFE
восстанавливаем содержимое #FFFE и #FFFF;
разрешаем прерывания если были разрешены.
# запоминаем ячейки #FFFE и #FFFF;
а вдруг еще и стек нельзя трогать? а то куча ограничений на все, а на стек нет... вдруг он не инициализирован и указывает в пзу или в программу, или на 0 (и тогда #FFFE перепишется)... мутная задача какая-то.
Alex Rider
17.06.2012, 23:42
а вдруг еще и стек нельзя трогать? а то куча ограничений на все, а на стек нет...
А реально отдавать управление чужому заинкбиненному коду (не понимая как он использует стек) в тот момент, когда на стеке нету двух байт для перезаписи или стек настроен так, что при первой же записи перепрыгиывет через 0? Я не о технической возможности, а о вероятности натолкнуться на такое.
что значит реально ли? я думаю человеки много раз так делали (по ошибке). по-нормальному, всегда нужно знать какую-то доп. информацию, чтобы не возникало сюрпризов. вон куча пакеров распаковщики пихает в #5Bxx, а там то переменные 128к бейсика, то резидент sts... т.е. имхо задача требует уточнения условий.
Alex Rider
18.06.2012, 13:39
http://zx.pk.ru//showthread.php?t=17811
Про "по ошибке" - согласен. Если контекст исполнения чужого кода настроен ошибочно, понять, что же не так, мучительно тяжело. Другое дело, что внешний (поставляемый) код должен налагать минимум разумных ограничений на вызывающий код. На мой взгляд, ограничения в виде наличия свободной памяти по жестко заданным адресам (при релоцируемом коде), "невидимое" знакоместо над адресом #4000, разрешение/запрешение прерываний и их режим, наличие стандартной прошивки BASIC 48, включенной в момент вызова кода - ограничения неразумные. А вот наличие двух свободных 2-х байтов на стеке, расположенном не по адресам 0-1 - это разумное ограничение, которое, кстати, обычно никто не упоминает (вспомним, к примеру, депакеры и плееры, работающие в демах при жестких условиях - нигде не оговаривается максимальная глубина стека). Да и не задача это вообще, а просто вопрос по технике программирования.
имхо задача требует уточнения условий
Согласен, в постановке задачи, где речь идет про незнание контекста, можно было бы сказать, что я, как автор вызываемого релоцируемого кода, рассчитываю на какое-то разумное пространство на стеке.
вроде бы определились с условиями?
самый простой разумный вариант
если это спрайт граббер значит на экране графика
значит использовать экран можем
ld de,(#4000)
ld hl,#e9e1
ld (#4000),hl
call #4000
this
ld (#4000),de
;hl=this
Alex Rider
18.06.2012, 15:49
если это спрайт граббер значит на экране графика
значит использовать экран можем
Грабер умеет выгружать код вывода спрайта вместе с вырезанными спрайтами, чтобы, например, AER, мог вставить его в дему. Вот для этого кода и надо определять адрес чтобы посчитать где сами спрайты находятся. Так что предпочту вариант с #FFFE, а не с #4000
---------- Post added at 15:49 ---------- Previous post was at 15:46 ----------
Остался вопрос с глубиной.
например 5кБ стек как сделать?
А никак нельзя сделать ограничить размер стека. Можно занести в SP значение, на 5 Кб большее, чем последний используемый программой адрес, и писать код так, чтобы в стек не попадало больше 5 Кб. Только 5 Кб для стека - это зачем? BASIC резервирует себе максимум 80 байт, например.
Вспомнилось вдруг как без стека и CALL адрес выяснить - по ключу с помощью CPIR.
Чем длиннее ключ - тем надёжней, но и код (который определяет) тоже будет увеличиватся пропорционально.
Этого можно избежать сделав ключ цикличным, но всё равно размер ключа должен быть ощутимым, иначе надёжность падает (вдруг такой блок чисто случайно ещё где попадётся?).
по ключу с помощью CPIR.
а можно набросок кода?
а можно набросок кода?
Ну как-то так (не тестил, не пробовал, именно набросок)
LD DE,#0 (ну или откуда начинать? Если с RAM, то #4000)
M1:
LD BC,0
LD A,"K"
CPIR
INC HL
LD A,(HL)
CP "E"
JR NZ,M1
INC HL
LD A,(HL)
CP "Y"
DEC HL
DEC HL
JR NZ,M1
INC HL
INC HL
INC HL
JP (HL)
DB "KEY"
.... (сюда по-идее должен прыгнуть PC и в HL этот-же адрес)
Как-то так.
Может быть жестоко ошибаюсь.
Сейчас тестить некогда, но в своё время ковырялся с определением адреса - ностальжи увидев тему :) Позже обязательно попробую. Или топикстартер пусть поковыряет - расскажет работает или нет. Если что расскажу идею про цикличные ключи (пример приведён с тремя знаками, это очень мало, а много знаков делать - код раздуется. есть мысли как его сделать в цикле)
А можно 2-х байтный ключ использовать. Если в памяти будет найдено два совпадения, то просто увеличить ключ на единицу и искать еще раз.
(А нет. Адресации относительно IP нет. Ключ не увеличить.)
(А нет. А нет. Мы можем после нахождения ключа увеличить его и тем самым проверить, наш ли это ключ.)
Вот самое оригинальное решение топика :). Алгоритм, естественно, можно модифицировать. Но идею я постараюсь до вас донести, спектрумисты.
Процедура, естественно, релоцируема.
Прерывания можно и не запрещать, если вы уверены в том что не данная процедура не модифицирует код процедуры обработки прерываний и вызываемых ею подпрограмм в неподходящий момент.)
В общем, эта процедура ищет в памяти последовательность #18,#02
и если находит, то обнуляет смещение, и если получается что она модифицировала сама себя, то IX будет указывать на нашу программу и мы его скорректируем с PC, а если модифицировала что-то иное - то тут же восстановит данные и поиск продолжится.
;тут надо бы запомнить режим прерываний, процедуру писать не буду
di
;а можно и не запрещать прерывания по вышеизложенным причинам.
ld ix,#4000 ;начинаем искать в памяти с этого адреса
bad ld a,#18 ;ищем код JR
not_jr cp (ix+0) ;в памяти лежит JR?
inc ix
jr nz,not_jr ;если не JR, поиск далее
ld a,(ix+0) ;иначе берем смещение за JR
cp 2 ;сравниваем с величиной относительного смещения нашего JR
jr nz,bad ;оно не такое как у нас?
ld (ix+0),0 ;если значение такое же, то уничтожаем смещение
DEFB #18 ;вот тут после JR (код #18)
DEFB 2 ;cмещение указывает через ловушку
jr trapped ;<----сюда попадем если обнулили искомое смещение.
ld (ix+0),a ;иначе попадаем сюда. восстанавливаем смещение
jr bad ;и снова ищем JR
trapped ;cюда попадаем, если уничтожили нужное смещение
ld (ix+0),a ;восстанавливаем байт смещения
;IX установлен на байт смещения
LD DE,16
ADD IX,DE
;вот и все. PC вычислен
;IX=$ !!!
;тут можно восстановить режим прерываний, если изменяли его ранее
Вот самое оригинальное решение топика
Я прикидывал такой вариант, но он не подходит если в код в области ROM лежит.
Там ведь менять нельзя?
Или я опять не понял требований темы?
А если в общем плане (при нормальных условиях, без извратов с неизвестным стеком, ROM, IM и т.д.) - то это самый лучший вариант.
А если в общем плане (при нормальных условиях, без извратов с неизвестным стеком, ROM, IM и т.д.) - то это самый лучший вариант.
Я извиняюсь, но это самый подходящий вариант при любых условиях :), за исключением работы этого варианта из ПЗУ. Но такого условия не стояло. Условие стояло - "пзу может отсутствовать" :).
Да, пзу может отсутствовать. Прерывания мы запретим. Стек может быть где угодно (неизвестен) и режим прерываний может быть любым (IM 0,1,2). А состояние прерываний (enable/disable) может определить, сохранить и восстановить даже зеленый нуб.
Alex Rider
22.06.2012, 11:27
Вот самое оригинальное решение топика .
Круто! Гениально! Респект! Спасибо! Буду использовать этот вариант.
есть один минус значительный: например, если на прерываниях висит музыка, мы их запрещаем на очень долгое время - будут завывания... так что, все же, не любые условия.
Alex Rider
22.06.2012, 12:03
есть один минус значительный: например, если на прерываниях висит музыка, мы их запрещаем на очень долгое время - будут завывания... так что, все же, не любые условия.
Да, не учел этого... Да и время вызова получится существенное. Придется идти на такие хитрости и ограничения:
1) во время поиска прерывания не запрещать, запрещать только на момент проверки адреса;
2) сделать настройку на адрес при первом вызове;
3) запретить перемащать код в памяти после первого вызова (ну или делать проверку по ключу на перемещение и повторять настройку при перемещении).
и весь этот геморрой, чтобы не юзать стек (который наверняка можно юзать) и не портить экран на неск. тактов (которые даже не увидишь).
есть один минус значительный: например, если на прерываниях висит музыка, мы их запрещаем на очень долгое время - будут завывания... так что, все же, не любые условия.
При необходимости процедуру можно ускорить в разы и даже в десятки раз.
Например в памяти располагаем ключ из одинаковых байт X длиной в N. При поиске ищем байт X с шагом N. Если нашли, определяем его хвост. Меняем хвост. Если это наш измененный хвост, значит мы нашлись.
При необходимости процедуру можно ускорить в разы и даже в десятки раз.
только запрос на прерывание обычно длится 28 тактов.
есть один минус значительный: например, если на прерываниях висит музыка, мы их запрещаем на очень долгое время - будут завывания... так что, все же, не любые условия.
В самом худшем случае будет одно завывание. Т.к. если PC один раз определен, то всю процедуру можно свернуть в "LD HL, const; RET"
Если оценить по времени процедуру Лас, то в наиболеевероятномслучае (если вся память не забита кодом #18) время поиска составляет ~64k*40 тактов, что порядка 0.6-0.7 с.
Вот модифицированная на ускорение программа Лас'а:
ld ix,#4000 ;начинаем искать в памяти с этого адреса
ld de,#0010 ;шаг поиска
bad ld a,#18 ;ищем код JR
not_jr cp (ix+0) ;в памяти лежит JR?
add ix, de
jr nz,not_jr ;если не JR, поиск далее
; докатываем до конца блока из #18
loop18 inc ix
cp (ix+0)
jr z, loop18
dec ix
ld a,(ix+0) ;иначе берем смещение за JR
cp 2 ;сравниваем с величиной относительного смещения нашего JR
jr nz,bad ;оно не такое как у нас?
ld (ix+0),0 ;если значение такое же, то уничтожаем смещение
jr block
; #18 15 раз.
DEFB #18,#18,#18,#18,#18,#18,#18,#18,#18,#18,#18,#18,#1 8,#18,#18,#18
block DEFB #18 ;вот тут после JR (код #18) - 16й #18.
DEFB 2 ;cмещение указывает через ловушку
jr trapped ;<----сюда попадем если обнулили искомое смещение.
ld (ix+0),a ;иначе попадаем сюда. восстанавливаем смещение
jr bad ;и снова ищем JR
trapped ;cюда попадаем, если уничтожили нужное смещение
ld (ix+0),a ;восстанавливаем байт смещения
;IX установлен на байт смещения
LD DE,16
ADD IX,DE
;вот и все. PC вычислен
;IX=$ !!!
;тут можно восстановить режим прерываний, если изменяли его ранее
Здесь имеем скорость перебора ~ в 16 раз более высокую.
Alex Rider
22.06.2012, 18:25
Однако, я погорячился. Вариант Лас'а замечателен, но будет положен в копилку. Все-таки, для реальной задачи определение адреса через использование #FFFE и #FFFF выглядит наиболее применимым, но появившаяся идея о самонастройке и проверке перемещения по ключу тоже заслуживает внимания и будет реализована. Думаю, на этом варианте можно остановиться. Всем спасибо.
Alex Rider, Я думаю Вам надо всё-таки определиться - чего Вы хотите достичь. Банально и достаточно будет для Вашего случая попробовать один из тех простых вариантов, который Вам был предложен, и тот - который будет работать - и использовать. Я не думаю, что спрайтогенерилка, запускающая ваш код каждый раз будет другая. И вообще, по уму непредсказуемые проблемы надо решать по мере их поступления.
Лично мне видится вариант с #4000 и последующим восстановлением ячейки самым правильным и рабочим для 99,99999% случаев, и рекомендую Вам его и использовать. Если же модификация памяти для Вас по религиозным признакам неприемлема, то лучше ключевого способа по длинному 16-байтному ключу, сгенерированного неким генератором случайных чисел у вас нет и не будет.
Предлагаю закрыть эту тему, поскольку в настоящее время считаю её максимально раскрытой: используйте один из предложенных вариантов.
Alex Rider
26.07.2012, 13:38
Предлагаю закрыть эту тему
Полностью согласен. Будет использован метод с pop hl: jp (hl), но по адресу #FFFE. Всем спасибо.
Powered by vBulletin® Version 4.2.5 Copyright © 2026 vBulletin Solutions, Inc. All rights reserved. Перевод: zCarot