Вход

Просмотр полной версии : Чья фишка? (приём программирования)



Denn
27.07.2017, 16:50
Намедни дизассемблировал одну программу нашего соотечественника, столкнулся с интересным приёмом написания кода. А именно, поочерёдное перемежение кода и текстовых сообщений, причём без какой-либо явной адресации. Выглядит это следующим образом:



<код программы>
CALL PR_MSG
<текстовое сообщение #1/0>
<код программы>
CALL PR_MSG
<текстовое сообщение #2/0>
<код программы>
...и т.д.

PR_MSG:
; п/п вывода сообщения
; вход: [PC]-адрес начала сообщения
; выход: [PC]-адрес начала кода продолжения программы
POP H
CALL 0F818H; п/п монитора вывода сообщения по [HL]
PUSH H
RET


Чем примечательно. Для нубов затрудняет дизассемблирование :) - куски кода и текстов не имеют меток, на них нет никаких ссылок где-либо в коде. Передача и возврат параметров через регистровую пару [PC] (просто необычно, нетипично и прикольно).

Интересно, это фишка конкретно человека писавшего прогу, или это общеизвестный (читай - сдёрнутый с западных кодеров) приём, а я просто не в курсе?

bigral
27.07.2017, 17:25
Это стандартный прием для систем с общим адресным пространством кода и данных. Так называемый ON ERROR GOTO. Обычно так оформляют обработчики ошибок которым неважно состояние регистров (контекст) все входные данные захардкоженны прямо после этого CALL, в том числе и адрес дальнейшего выполнения программы после отработки обработчика. Только уважателей структурного программирования такие куски ставят в ступор. Бывают еще фукции с многими точками входа, co-routines, код неиспользующий стек, код оформленный только в виде обработчиков прерываний, код действие которого заключается в создании нужной последовательности сигналов на шине адреса\данных а не манипуляции регистрами I/O и т.д. и т.п.

Denn
27.07.2017, 17:33
Это стандартный прием для систем с общим адресным пространством кода и данных.

В общем-то, все наши (и не очень) восьмибитки таковыми системами и являются. Однако я впервые встречаю такой приём! А дизассемблировал я всякого немало.

- - - Добавлено - - -

Кстати, конструкцию:



PUSH H
RET


я бы заменил на простое:



PCHL


Тут явно делали "для красоты" :)

barsik
27.07.2017, 17:35
Это общеизвестный и распространённый приём. Он экономит ОЗУ и удобен, т.к при наборе не надо искать место где-бы втиснуть текстовую строчку. Я так делаю уже более 20 лет. Только я использую RST 18H. Заимствовал из ROM-BIOS ГДР-овских компьютеров (а также и другие RST). Эти RST встроены во все мои ДОС (блок RST кидается на адрес 8 при WARM BOOT DOS), т.к в большинстве ДОС на КР580/Z80 ZERO-page именно для этого и резервирована. Также встречал этот приём при дизассемблировании многих известных CP/M-программ. Этот приём также использован в многих программах известного программиста для ОРИОНА В.Сугоняко. Привожу вариант, как это встречается во многих фирменных CP/M-программах в варианте для CP/M уже не просто в дизассемблированном виде, а в том виде как это было в исходниках (т.е реконструкция). Хотя RST гораздо выгоднее. Использование макро порождает иногда довольно глупый код, например CALL, а затем RET. Хотя (т.к мы не на 80x86) на 8-ми разрядках просто ставят JMP вместо CALL и последующего RET.



.
fmssg EQU 9

CPM MACRO PARAM
LD C,PARAM
CALL 5
ENDM

MSSG MACRO PARAM
LOCAL M1
CALL M1
defb PARAM
defb '$'
M1: POP DE
CPM fmssg
ENDM

CRMSSG MACRO PARAM
LOCAL M1
CALL M1
defb 13,10
defb PARAM
defb '$'
M1: POP DE
CPM fmssg
ENDM

Пример использования в тексте:

CRMSSG 'Здравствуйте Denn !'


Если используется вариант CALL, то это затрудняет дизассемблирование не только неопытным, а всем (я так понимаю, что мерзкое слово нуб означает неопытный). Если использовать RST 18H, то это по крайней мере сразу обнаруживается (а если дизассемблер позволяет, то это можно даже автоматизировать).




.
; портит ячейки 8...40H

BEGRST:
;INPUT:
PUSH HL ; RST 8 ;1
PUSH DE ;1
PUSH BC ;1
CALL CONST ;3
JR @INPUT ;2

;SCOUTA:
JP SCOUTA ; RST 10H ; 3

TXTOU1: CALL SCOUTA ; 3
JR TXLOOP ; 2

;TXTOUT:
EX (SP),HL ; RST 18H ; 1
TXLOOP: LD A,(HL) ; 1
INC HL ; 1
OR A ; 1
JR NZ,TXTOU1 ; 2
EX (SP),HL ; 1
RET ; 1

;STATUS:
PUSH HL ; RST 20H ; 1
PUSH DE ; 1
PUSH BC ; 1
CALL CONST ; 3
JR POP_RG ; 2

;RDKBRD:
JP YF81B ; RST 28H ; 3
DS 1 ; 1

POP_RG: POP BC ; 1
POP DE ; 1
POP HL ; 1
RET ; 1

;CONIN:
PUSH HL ; RST 30H ; 1
PUSH DE ; 1
PUSH BC ; 1
CALL CONIN ; 3
JR POP_RG ; 2

JP CCP ; RST 38H

@INPUT: OR A
CALL NZ,CONIN
JR POP_RG

RSTLEN EQU $-BEGRST
@ENRST EQU 8+RSTLEN


Этот файл кидается командой LDIR на адрес 8 в DOS, а также вообще в любых программах. После чего доступны вызовы по RST. Я RST не придумывал (не конкретно код, а назначание конкретных RST), а заимствовал, отчего получил совместимость. Есть вариант блока RST и для КР580 (но он более громоздкий, менее удобен для иллюстрации). Здесь посмотрите на RST 18H.

Думаю, что такой приём передачи параметров удобен для передачи параметров в ЯВУ для процессоров у которых хреново со стеком (в частности, 6502 и 6800, про другие подобные CPU просто не знаю). Отчего, наверное, трудно делать компиляторы ЯВУ в которых данные передаются через стек.

Denn
27.07.2017, 17:49
Это общеизвестный и распространённый приём. Он экономит ОЗУ...

Отлично, я это и хотел узнать. Спасибо



Этот приём также использован в многих программах известного программиста для ОРИОНА В.Сугоняко.

Можно пример? В его ORDOS-программах я такого не встречал.

HardWareMan
27.07.2017, 17:52
Denn, подсмотрел в 89м году в дизасме ED^7000 для Специалиста (дизасм делал сам, Микроном, на кружке). Использую тоже часто. По вышеуказанным причинам. Я использую XTHL перед кодом и перед RET, потому как она автоматически заменяет пару PUSH H / POP H.

; Печать сообщение до 00h из [SP]
Print_LCD_SP: xthl ; Берем адрес в [SP]
push psw ; Сохраняем [A]
Print_LCD_SPL: mov a,m ; Берем символ
inx h ; Следующий символ
cpi 00h ; Уже все?
jz Print_LCD_SPE ; Да - выход
call Print_LCD_Sym ; Печатаем символ
jmp Print_LCD_SPL ; Крутимся
Print_LCD_SPE: pop psw ; Восстанавливаем [A]
xthl ; Возвращаем адрес в [SP]
ret ; Выходим

Denn
27.07.2017, 18:04
HardWareMan, тоже вариант. Только CPI 00h очень режет глаза :)

HardWareMan
27.07.2017, 18:07
HardWareMan, тоже вариант. Только CPI 00h очень режет глаза :)
Код не претендует на идеальность. Он работает, печатает до 0 (часто используется даже в С), для DOS обычно используется символ '$' (0x24). Замени CPI 00h на CPI 24h и будет как в DOS.

PS А, понял, если до 0 то можно же ORA A. Ну да, можно выкинуть байт.

barsik
27.07.2017, 18:17
Можно пример? В его ORDOS-программах я такого не встречал
Например, это есть в программах BRU.COM и BRU4.COM для CP/M в банке 0 (т.е на 48К). Прилагаю исходник оригинала BRU4 и ORD5. ORD5 это незаконченная попытка доработки, чтобы ввести туда окна без использования оконного драйвера.

Несколько слов о именах и расширениях. Как все знают в ORIONSOFT использовали странное расширение BRU, тогда как все остальные использовали логичное расширение ORD. Естественно, ORD и BRU устроены одинаково (ORDOS метка файла в 16 байт прилеплена в начале файла, что необходимо, чтобы сохранить адреса загрузки и длину). В начале исходника BRU4 есть переменные EX1 EX2 EX3. Они задают 3 буквы O R D или B R U (а в ORD5 есть ключ ORD, его поставьте 0 или не 0, это быстрее, чем менять буквы). Как поставите, с такими файлами и будет работать. И соответственно тогда и имя сделайте ORD4/5 или BRU4/5.

Там же, чтобы можно было посмотреть, в архиве есть CP/M для банки 0, которая работает без НГМД (VDISK из излишнего ОЗУ банок 2 и 3 (а может и 2...7, не помню). Т.е она может быть использует ОЗУ более чем 256К и заработает только, если есть ОЗУ 512К, но может быть использует только банки 2 и 3. Я после это проверю, и если надо, найду ДОС для банки 0 и ОЗУ 256К.

Так вот с помощью этой RAM-CP/M можно увидеть эти программы (т.к она без драйвера, аналог CP/M-48К ОРИОНСОФТ). Транслируется не в COM, а в ORD-формат, чтобы можно было использовать в бездисководной системе. Тогда файлы попадают на эл.диск CP/M автоматически загружаясь из квазидисков ORDOS, для чего в квазидиске 'B' д.быть файл !ZAG. CP/M для банки 0 ищет при старте этот файл, и, если он есть, то все файлы ORDOS перегружаются из квазидисков ORDOS в эл.диск CP/M. Если надо странслировать прямо в COM-файл, то забейте в начале исходника две строчки ORDOS-метки.

NEO SPECTRUMAN
27.07.2017, 19:47
Однако я впервые встречаю такой приём!
в Legend of Zelda: Link’s Awakening 93 года для GB используется нечто(сам подход) подобное
так что большие фирмочки тоже так делают

bigral
28.07.2017, 16:27
Я про такое узнал из файла рекомендаций к проге - перекомпилятору MSX1 картриджей под MS-DOS. Игры на MSX1 картриджах такое часто содержат.

Denn
28.07.2017, 16:29
Ясно. В общем, можно смело юзать без зазрения совести =)

HardWareMan
28.07.2017, 22:41
Denn, да, и запутывание дизассемблера тут ни причем. Просто удобно, т.к. текст всегда под рукой и не надо заводить под него отдельную метку. Кстати, прием работает не только с текстом. ;)

Denn
29.07.2017, 12:42
HardWareMan, вот как ситуацию вижу я. В связи с интересом исключительно к 8-биткам и их миру той эпохи, меня не интересуют средства разработки (и реверс-инжениринга) на писи, поэтому я не в курсе что умеют современные навороченные дизассемблеры (возможно там уже подключен искусственный интеллект, который сам разгадывает хитрозапутанные задумки кодеров?), а пользуюсь по-старинке простым дизассемблером на 8-битке (мне так "спортивнее"). Ранее (давно) это был дизассм из пакета Микрон, потом я от него "устал", "психанул" и написал свой, для меня более удобный.
Так вот, всё что умеет дизассемблер в плане сервиса: разделять код и блоки данных, последние ему надо указывать в явном виде, т.е. их человек заранее сам должен вычислить и указать дизассму участки адресов, где не код, а DB'шки.

В результате имеем следующее. Идеальный случай - когда прога состоит только из кода (голову греть не надо вообще, кормим исходник дизассму и он всё делает сам, в лучшем виде). Хороший случай - мухи и котлеты отдельно, т.е. есть блок кодов, а в конце болк данных, причём данные текстовые, начало которых легко и непринуждённо обнаруживается по "F3": <код>... <здесь был Вася/0>... end. Мы указываем дизассму на этот блок данных и с радостью обнаруживаем где-то в коде полученного листинга что-то в духе: LXI H,M_TEXT; CALL MF818. Собственно, на этом халява заканчивается.

Далее идут более сложные случаи, когда посреди кода встречаются переменные, данные (нетекстовые). Частично такое вычисляется по "F3" опытным глазом (со временем уже в кодах понимаешь, где осмысленный код ВМ80, а где "какая-то лажа"). Но одиночные вставки констант/переменных всё равно невозможно обнаружить беглым взглядом, поэтому приходится дизассемблировать многократно, причём каждый раз довольно глубоко вникая в код.
Иногда лажу вижно практически сразу, когда внезапно начинается несвязный набор команд. Но, как правило, явным признаком лажи является т.н. "орфан" код, т.е. блоки кода или данных, идущие после RET или JMP, на которые дизассм не сгенерил метки, т.е. нигде в коде на эти блоки нет переходов/ссылок. По логике таких блоков быть не может (кроме всяких сигнатур разработчиков).
Геометрически растёт геморрой при многократном перемежении кода/данных.

В сабжевом случае как раз и получается, что вроде как код логически связный, данные осмысленные, но они многократно перемежаются и не имеют меток. По-началу вообще полнейший ступор! Особенно удивляет незавершённый код: CALL XXX; <текстовое сообщение>. Понимаешь, что вот вообще нифига не понимаешь :)
Даже самомодифицирующийся код такого ступора не вызывает.

Eltaron
29.07.2017, 13:03
Denn, понятно, что этот прием усложняет дизассемблирование. Но вряд ли автор ставил перед собой такую задачу. Он-то ведь собственную программу не дизассемблером читал. Да и зачем усложнять дизасм основного блока? Пират его, очевидно, не читая скопирует, ему будет до лампочки, перемешан там код с данными или нет. Обфусцировать имело бы смысл блок загрузчика или там декомпрессора.

Ну и наверняка же у автора был макрос в духе


.macro PRINT(x)
CALL PRINT_MESSAGE
DEFM x
.endmacro


и он печатал все сообщения простой читаемой конструкцией PRINT("Hello, World"). Это очень удобно и код становится намного понятней, чем если бы там были ссылки на сообщения из отдельно лежащего массива строк.
На самом деле это и без макроса намного более читаемый код. А то, что читаемость кода повышает скорость разработки - это общеизвестно.

HardWareMan
29.07.2017, 14:47
Denn, ну я когда ED^7000 разжевывал на Специалисте под Микроном (хотел поменять местами действия РУС/ЛАТ и СТР, в редакторе РУС/ЛАТ выбирал регистр а СТР язык, что не логично ваще) сделал проще. Мы как раз только закончили подключение Консула к Спецу, я просто взял и скормил Консулу весь листинг из дизасма. Потом посидел пару дней над листингом, делая пометки и, в итоге, в редакторе текстов Практик (да-да, редактировать кишки редактора им же самим :)) делал соответствующие исправления. Затем собрал ассемблером и все работало. Ну а дальше дело техники. ;) Кстати, сборка 2КБ кода (~28КБ текста) занимает примерно минут 5.

barsik
29.07.2017, 15:06
мне так "спортивнее"... был дизассемблер МИКРОН, потом он меня "достал" и я написал свой дизассемблер, для меня более удобный
Тяжёлый Вы выбрали вид спорта.

Мы тут конечно все "сходим с ума по своему" и я тоже люблю использовать только свои программы (например, хотя это уже и не требуется, пишу свой ассемблер для 6800, впрочем это несложно, т.к ранее написал ассемблер 6502, переделка проста). Это даёт контроль и возможность добавить те свойства, которых нет в чужом ПО (например, разве может чужой ассемблер транслировать исходник для КР580 в объектный код процессора 6800?).

Но всё-таки для спорта разумно выбирать интересные задачи (творчество). А дизассемблирование примитивным дизассемблером - это удовольствие ниже среднего, а для программы написанной извращённо, иногда это просто неразрешимая задача. Это совершенно неинтересно, это просто "убой своего времени", т.к можно многими днями трахаться, портить себе нервы, а в итоге работа сводится к тому же самому лобовому анализу программы. А почему тогда не дизассемблировать вручную с помощью таблиц кодов, это ещё более экстремальный спорт?

Задачу интерактивного дизассемблирования решил в 1979 году Вард Христенсен, написавший RESOURCE для КР580 (позже другие люди сделали версию и для Z80). А интерактивная IDA ещё удобнее, т.к умнее и спасает от многих ошибок. При интерактивном дизассемблировании большой плюс в том, что распознав назначение подпрограмм или участков кода, мы сразу назначаем осмысленные имена меток, в итоге получается почти исходник. В итоге, то что примитивным дизассемблером делается за многие дни каторжного труда в IDA делается за пол часа.

Грамотный дизассемблер должен для каждого "сиротского" куска кода искать нет ли загрузки этого адреса в регистры, например в HL (часто делается LD HL,ADDR : PUSH HL, чтобы по RET из разных ветвей выполнить один фрагмент, обычно POP-ы). А может ли Ваш дизассемблер помочь, когда совершенно осмысленно (ради экономии байтов) делается JMP в середину кода команды?

По счастью искусственного интелекта в дизассемблеры ещё не добавили. Чтобы из любого кода мгновенно получать исходник с комментариями и осмысленными метками. Скорее всего, лет через 10 это будет, но к тому времени мы все или уже будем в "матрице" работать батарейками или терминаторы будут добивать последние остатки человечества.

Это не шутка, т.к, несмотря на призывы фантастов и ведущих учёных, тьюринговая полиция до сих пор не создана, а безответственные инженеры на службе корпораций быстрыми темпами приближают апокалипсис, закончивая разработку враждебного человечеству искусственного интелекта.

Denn
29.07.2017, 16:08
да-да, редактировать кишки редактора им же самим :))

:)

У меня ещё веселее было. Я первый текстовый редактор писал в... машинных кодах! А потом на этом самом машиннокодовом редакторе писал (уже на ассме, разумеется) другой текстовый редактор, более продвинутый :) А потом, на этом продвинутом написал ещё более навороченный. Получилась такая цепочка саморождения текстовых редакторов :)

- - - Добавлено - - -


Но всё-таки для спорта разумно выбирать интересные задачи (творчество).

Разумеется, дизассемблирование это ни разу не творчество, но обучаться программированию нужно как-то было, вот и приходилось разбираться в чужих программах. Частенько конечно просто в кодах, но когда что-то масштабное и сложное, то лучше дизассемблировать. Это сейчас есть интернет, куча информации, что аж уже и не интересно, а раньше не то что "железный занавес", а я бы сказал глухой бункер был, программы набирались в машинных кодах, а некоторые дампы приходилось аж переписывать вручную в библиотеках!

- - - Добавлено - - -


интерактивная IDA ещё удобнее, т.к умнее и спасает от многих ошибок. При интерактивном дизассемблировании большой плюс в том, что распознав назначение подпрограмм или участков кода, мы сразу назначаем осмысленные имена меток, в итоге получается почти исходник. В итоге, то что примитивным дизассемблером делается за многие дни каторжного труда в IDA делается за пол часа.

Я не хаккер, поэтому в профессиональных системах дизассемблирования не нуждаюсь. Чужой код использую исключительно в образовательных целях. Например, разбирался с ВГ93 исключительно посредством анализа кода причинных мест SPDOS.

- - - Добавлено - - -


Denn, понятно, что этот прием усложняет дизассемблирование. Но вряд ли автор ставил перед собой такую задачу.

Действительно вряд ли. Скорее банальная экономия трёх байт при каждом выводе сообщения. Но красиво! Я бы вряд ли до такого сам додумался.
Вспомнилось, как когда-то было откровением нетрадиционное использование стека, кажется впервые я это встретил в п/п скроллинга экрана. Причём выяснилось вот как, сначала я написал свой скроллинг и с удивлением обнаружил, что монитор делает это в разы быстрее! Заинтересовался, начал разбираться (тогда ещё в машинных кодах), по-началу вообще не понял как это работает! )) Потом, когда "дошло", то прифигел конкретно )) Не иначе как Боги писали такой код!
Вообще, по молодости много было таких "открытий", славное было время.
Сейчас полная скукота: гугл, запрос, вот тебе всё на блюдечке. Желание что-то думать (и изобретать) отбивает напрочь. Времена тупой копипасты и собирания из готовых "кубиков" (не вникая). И в тоже время все всё знают, потому что почитали в интернетиках, но никто ничего не делает, потому что никому ничего не интересно и не нужно (кроме как читать эти самые интернетики :)).

barsik
29.07.2017, 17:24
Не иначе, как Боги писали такой код !
Нет не боги. Это был А.Ф.Волков.

А для ещё большей экономии байтов при выводе текстов в 7-ми битной кодировке удобно в качестве стоп-сигнала использовать не байт 0, а выставленный старший бит символа. Для этого достаточно чуть-чуть доработать п/п-мму F818 или написать свою п/п-мму MSSGH. Это используется во многих иностранных компьютерах, а в ассемблерах для них есть специальный оператор, который задаёт строку, как DEFB, но дополнительно устанавливает старший бит в последнем символе строки. В ассемблерах, дизассемблерах и компиляторах ЯВУ, т.е везде, где большие таблицы, это даёт экономию в килобайты.

Denn
29.07.2017, 17:48
Нет не боги. Это был А.Ф.Волков.

Когда ходишь пешком под стол (я тогда был школьник), то для тебя какой-то дядя (особенно которого опубликовали в журнале) и есть бог.



А для ещё большей экономии байтов при выводе текстов КОИ-7 удобно в качестве стоп-сигнала использовать не байт 0, а выставленный старший бит символа. Для этого достаточно чуть-чуть доработать п/п-мму F818 или написать свою п/п-мму MSSGH.

Я тоже так думал, но на практике экономия не получается. Только если в программе "100500" коротких текстов, причём все только на одном языке.
Подпрограмма F818h уже есть в мониторе, тобишь её код места не занимает, а MSSGH нужно писать свою, и это отнюдь не пара-тройка байт. В итоге код этой п/п больше, чем кол-во сэкономленных нуль-терминальных байтов во всех текстовых сообщениях.



Это используется во многих иностранных компьютерах

Ещё бы, там сам бог велел - у них же нет русских букв :)

HardWareMan
29.07.2017, 21:26
Я тоже так думал, но на практике экономия не получается. Только если в программе "100500" коротких текстов, причём все только на одном языке.
Таблица токенов практически во всех барсиках под ВМ80 так и устроена. ;) И еще таблица сообщений об ошибках.

Denn
29.07.2017, 23:30
Таблица токенов практически во всех барсиках под ВМ80 так и устроена. ;)

Так барсики - они ж все нерусские в девичестве. Кроме одного ))

OrionExt
30.07.2017, 00:18
Барсики не наш продукт – они все такие. Ток один мне вставляет MSX-котик:) Ток не будем от темы уходить. А поговорить хватает ресурсов.

Vadim
30.07.2017, 15:10
[QUOTE=Denn;921470]В общем-то, все наши (и не очень) восьмибитки таковыми системами и являются. Однако я впервые встречаю такой приём! А дизассемблировал я всякого немало.
- - - Добавлено - - -

Я такое встречал повсеместно, и сам то же так писал.

TomaTLAB
02.08.2017, 00:00
Это стандартный прием для систем с общим адресным пространством кода и данных.
И не только. На 51-ом нечто подобное часто использовал. И для сообщений, и для табличных преобразований.