Обновить
59

Пользователь

11
Подписчики
Отправить сообщение

есть ли что-нибудь действительно для начинающих?

Протеус же. Нарисовал принципиальную схему, сразу же перешёл в режим трпссировки/разводки, где программа проверяет соответствие топологии разводки схеме. Покрутил в 3D, помоделировал работу...

Для жителей бывшего СССР — бесплатная лицензия.

Чтобы поместить компонент на принципиальную схему, нужно создать его схематичное изображение. У этого контроллера 48 контактов и все они должны быть нарисованы.

А вот и нет. Не изображать пины питания у ОУ и логических элементов — классика жанра.

Также я представляю, как у какого-нибудь BGA-процессора, у которого только GND-пинов насчитаньмя штук 150, будут на принципиалке рисовать каждый пин отдельно...

Два резистора — это резистивный делитель, а делитель — это вообще любой делитель, в том числе, например, емкостный делитель, а так же делитель на основе нелинейных и активных элементов.

Кузнец вряд ли инженер.

Работником лёгкой промышленности может быть и ткачиха.

Кузнецы и сварщики не станочники, да и сварщик вряд ли мог сказать «я работаю в сфере металлообработки».

Станочники — аналог разработчиков. Далеко не каждый, кто называет себя айтишником, является разработчиком.

И почему сварщики, токари, кузнецы, фрезеровщики, литейщики, шлифовщики, конструктора, термисты и прочие не догодались назвать свою сферу «металлическими технологиями», а себя — MT-специалистами или эмтишниками?

у меня еще долго бомбило от текстового инсталлера NT ...который сравнивая с 95 и особенно с 98 выглядит крайне старомодно на...и это тянулось аж до XP включительно

А что, если я скажу, что мне больше нравился и сильно нравился именно текстовый вариант?

Вы наверное из тех людей, кто молится на циферные спидометры и сенсорные кнопки на панели управления, а стрелочные приборы (которые считываются периферическим зрением) и физические осязаемые кнопки и крутилочки (которыми можно управлять вслепую) считаете старомодными?

Автор упомянул, что писал на UnrealScript, при этом в числе мозговынносящих языков ему нет отдельно посвященного параграфа.

Этого я не понимаю. У меня был опыт с этим ЯП — я не знаю до чего он дорос или скатился сейчас, я говорю о языке образца 98-го года.

И до сих пор у меня именно от него сильнейший вау-эффект.

С одной стороны, код на этом ЯП прозрачно и непринужденно (динамически) линкуется с функциями, классами (методами классов) и операторами, написанными на C++ и живущими в DLL. Это значит, что под капотом у языка такое же простое и прчмолинейное ABI, как у C/C++, где int-переменная — это реально всего лишь 4 байта, а не хренов контейнер с кучей полей о типе, владельце и инфой о том, кто на ком стоял. Чуть допилив исполнительную среду, можно, по идее, и вызов WinAPI было сделать таким же прозрачным и непринуждённым, как вызов соседней процедуры.

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

Это значит, что упомянание класса или ссылку на класс (именно на сам класс, а не на экземпляр класса) можно записать в переменную, эту переменную передать куда-нибудь в функцию, а там внутри переданное значение могут передать в качестве операнда оператора new (правда там он назывался Spawn, а не new). Причем можно объявить переменную или аргумент так, что в неё можно будет записать только классы, являющиеся подклассами определенного базового типа.

Стейты у объектов из коробки: еще одна примечательная функция. Если у класса 7 методов и объект может пребывать в 3 разных состояниях, не нужно в каждом из 7 методов делать switch или каскад if-ов: можно, используя синтаксические возможности, определить 3 стейта, которые породят 3 скоупа, в каждом из которых можно написать собственную имплементацию метода, специфичную для данного стейта.

Я бы хотел такой язык с такими фишечками и таким же приятным синтаксисом для написания general-purpose программ и прикладного софта. Особенно если подружить его ООП-механизм с COM.

Спасибо. Зачастую нужен волшебный пинок, да.

Я не увидел бирки, что это перевод. Но если это таки галлюцинаторный перевод, тогда у меня и к планам питания вопрос. Возможно это power planes, а power planes это совсем не планы питания...

На слове «проводники-инсуляторы» я чуть лицо фейспалмом не пробил...

в одном OpenProcess без явного вызова WriteProcessMemory ничего плохого нет.

Это единственное, что стоило бы контролировать антивирусом, аудитам. Без полученного хендла с правом на соответствующие операции никакие ни WriteProcessMemory, CreateRemoteThread, ни SetThreadContext ничего сделать не могут.

А если контролировать последние — это лишние накладные расходы, потому что подтормаживаются совершенно легитимные вызовы указанны системных сервисов.

Так что ключевой момент это как раз OpenProcess.

уж комментарий-то точно гораздо меньше людей увидят (а тем более прочтут) так что в сравнении с ним статья гораздо нужнее :) и раз уж на него времени хватило то можно попытаться

Комментарий я обычно строчу даже не перечитывая. А статья это 300 итераций с перепрочтением, перестановкой абзацев, укорочением одних предложений, уточнением других, рисованием картинок.

т.е., по сути, сегментация в сколько-нибудь массовых ОС, включая Винду и Линух, не используется

Но это не совсем так.

Windows в user-mode создаёт небольшой сегмент с базой, указывающей на thread environment block (TEB) / thread information block (TIB), а селектор этого сегмента загружает в регистр FS.

Отсюда обращения к FS:[0] для работы с SEH-цепочкой, работа виндового TLS на это завязано, да и куча кода в kernel32.dll и иже с ними лежет в TEB для множества разных целей.

В Linux-е FS используется для TLS, а GS доступен для использования.

В этом объёме сегментацию не выпилили даже в AMD64.

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

Да, может постоянная замена значения сегментных регистров и вызвала бы проблемы с производительностью (из-за необходимости процессора обращаться к таблицам дескрипторов, чтобы занести что-то в теневую часть сегментных регистров), но уверен, Intel могла это красиво оптимизировать, будь на технологию ответный спрос, как она оптимизировала трансляцию линейных адресов в физические (речь о TLB).

Подумать только: у нас был бы мир с аппаратной защитой от ошибки переполнения буфера, мир, где программу можно было бы разбить на множество изолированных друг от друга микро-песочниц, где функция не могла бы из себя вызвать какую угодно другую функцию процесса и обратиться к каким угодно другим данным процесса, а «видела» бы только минимально необходимый для своей работы набор данных и других функций/процедур. Ошибки переполнения буфера, шеллкод и инжект перестали бы существовать или потеряли бы смысл...

OpenProcess -> VirtualAlloc -> [VirtualProtect] -> WriteProcessMemory -> CreateRemoteThread

Это скучно и невероятно шумно для средств защиты.

Но затем у вас идёт

hProc = OpenProcess(PROCESS_VM_OPERATION, 0, pid);

Как будто бы это не шумно для защиты :-D

С SetThreadContext в общем-то очевидно, а вот из способов протащить собственный код в чужой процесс незаметно, меня до сих пор очаровывает вот этот.

потому что статья - краткий обзор а не учебник от Питера Нортона с демонстрацией создания шестнадцатеричного редактора для дисков в объёме 200 страниц.

Никто не спорит, но приводя пример ARM-кода вы всё же используете константы GPIO0DIR и GPIO0DATA вместо магических чисел 0x50008000 и 0x50003FFC, в примере с AVR8 вы используете константы PORTD и DDRB вместо магических 0x12 и 0x17 и так далее, но именно в случае x86 почему-то используются магические числа вместо констант с внятными именами. Вот именно к этой непоследовательности и вопрос.

однако что мешает Вам исправить ситуацию и написать собственную статью? в частности использовать бОльшую часть данного комментария как отправную точку.

Мешает гнетущение ощущение, что такая статья окажется никому не нужной и не интересной, недостаток времени и почти что неспособность бороться с тенденцией «и тут Остапа понесло» при написании статей (и комментариев тоже, но с комментариев спрос не такой строгий и ответственность не такая сильная).

На мобильных устройствах редактор комментариев это просто ад и пытка.

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

А при создании спойлера, например, бекспейс в заголовке спойлера срабатывает ровно на одну букву, после чего курсор перескакивае в тело спойлера. Хочешь поменять 10 символов за головке раскрывающегося спойлера — будь добр 10 раз рукой переставь курсор из тела спойлера в заголовок.

Не важно, что бОльшая часть посвящена ассемблерам для МК-шных архитектур. Вопрос в том, что как только вы коснулись x86-ассемблера, то сразу всплывают

mov ah, 9
int 21h

Во-первых, это создаёт ощущение того, что вы пересказываете статьи 30-летней давности, для которых такие сниппеты-примеры были актуальными. Хотя как раз вот такое подобное API (между прикладным софтом и ДОС-ом и между гипотетической ОС реального режима и БИОС-ом) морально устарело, а вот сам x86-ассемблер актуален как никогда, покуда жива x86-архитектура, пусть и в виде x86-64.

Во-вторых, это чисто эстетически не самый приятный и разумный способ организации API. Он исторически сложился, но не стоит начинать знакомство а написанием кода на ассемблере именно с демонстрации такого подхода. Почему бы в качестве примера не привести что-то вроде

push ...
push ...
push ...
push ...
call [MessageBoxA]

Это и актуально, и не так запутанно, и универсально, и пример можно скомпилировать и запустить как под Windows 95, так и под современной виндой. А потом уже в качестве шок-контента показать, как делалось взаимодействие с системными API в стародавние времена.

В-третьих, даже если закрыть глаза на неактуальность DOS-прерываний в реальном режиме, а представить, что это супер-актуально и что ничего другого нет — опять же, именно такой код в таком виде подаёт дурной пример. И за этим делом стоит, на самом деле, большой философский вопрос. Да, я знаю, что именно так (int 10h) в большинстве своём все и пишут (или писали). Но правильно ли так писать?

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

Когда я был юным и глупым, я бы уверенно ответил, что в первую очередь ассемблер это инструмент, который транслирует инструкции для процессора, написанные в человеко-читаемом виде (в виде текста из мнемоник инструкций и операндов) в машинный код в его непосредственном виде. То есть я бы сказал, что написать свой ассемблер — значит написать трансялятор, который для каждой строчки вроде xor eax, eax или nop или int 10h выплюнет в ответ байты 33С0, 90 или CD10. Но прошли годы, мне довелось и свой собственный x86-ассемблер написать, и написать just-for-fun реализацию компилятора Си , и я могу с уверенностью сказать, что трансляция текстового представления инструкций в бинарное представление это вообще ерунда и абсолютно тривиальная задача, а самое главное в ассемблере совсем не это.

А что же тогда? Тут стоит задаться вопросом: вот есть ассемблер для x86, для MIPS, для ARM, для AVR, а может ли существовать ассемблер вообще ни под какую архитектуру, то есть ассемблер, вообще не знакомый ни с одной из архитектур, не знающий ни одной машинной команды? Будет ли хоть какой-то смысл существования такого инструмента? Тогда можно поставить вопрос иначе: могу ли я взять какой-нибудь x86-ассемблер (например FASM, MASM, NASM) и с его помощью породить на свет машинный код для AVR8? Ведь набор инструкций совсем другой, а даже если какие-то мнемоники и совпадают, опкоды совсем другие и принцип кодирования операндов тоже совсем другой. На самом деле — могу, если просто проведу трансляцию текстового представления машинных инструкций в бинарное и в своём ассемблерном листинге запишу всё в виде директив .db. Нечестная игра? Очень тяжело? На самом деле, не очень-то и тяжело — написано один раз и работает всегда. А есть в ассемблерах (для любых архитектур) кое что ещё, без чего писать код, править и дорабатывать его было бы на порядок или на несколько порядков более тяжело, чем если бы конвертировать инструкции в бинарный вид пришлось бы вручную.

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

Вот это самое главное (по крайней мере я так считаю) в ассемблерах и самое сложное при написании своего ассемблера для какой бы то ни было архитектуры.

Почему сложное? Что здесь сложного?

А вот, например, что. Возьмём для простоты архитектуру x86. С точки зрения человеко-читаемого представления кода всё очень легко:

jmp <метка1>
или
jge <метка2>

С точки же зрения машинного кода как для безусловного джампа так и для всех условных джампов существует две версии: одна версия инструкции после которой процессор ожидает увидеть относительное смещение в виде 8-битного знакового числа, в другой версии смещение будет закодировано уже в виде 32-битного или 16-битного знакового числа (в зависимости от того, в 32-битном или 16-битном режиме выполняется задача, код которой выполняет процессор + к этому к инструкции может быть примерён префикс 66h переопределяющий размер адреса на противоположный).

Так вот, если мы пишем свой собственный x86-ассемблер, который идёт по строкам, перебирая одну за другой, и для каждой новой инструкцией делает трансляцию в машинный код, то здесь возникает проблема. Как ассемблеру обработать машинную команду вида jmp some_label? Выбрать для этого короткий форму кодирования с опкодом EB или длинную форму с опкодом E9? Предположим, мы генерируем код для 32-битного режима. Тогда инструкция с опкодом EB будет занимать 2 байта, а инструкция с опкодом E9 — 5 байт, из которых четыре это относительный адрес места назначения прыжка. Можно было бы поступать тупо и везде для всех джампов, как безусловных так и условных, использовать длинный вариант инструкции. Но это бы безмерно разувало машинный код, потому что ветвлений в коде чуть ли половина от всего объёма команд.

Можно поступать умнее: если jump destination лежит относительно недалеко и смещение относительно адреса следующей инструкции лежит в пределах от –128 до +127, то нужно использовать компактный двухбайтовый вариант инструкции, в противном случае — длинный трёх или пятибайтовый.

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

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

Проблема в том, что ни первый ни второй подход не работают. Если метка лежит впереди и джамп ведёт вперёд и мы не знаем, какое до него расстояние и у нас возникает неопределённость относительно длины текущей транслируемой инструкции, мы не можем тупо взять и прямо сейчас попробовать ассемблировать/транслировать последующие инструкции (с целью определить величину смещения), либо отложенным проходом расставить все точки над i, потому внутри этого прыжка могут оказаться другие джамп-инструкции с неопределённой длиной, а у тех, в свою очередь, destination-метка может быть выбрана так, что уже длина тех инструкций тоже будет в состоянии неопределённости и попадёт в зависимость от длины транслируемой в данной момент джамп-инструкции. Иными словами: длины джамп-инструкций зависят от того, насколько далеко нужно прыгать, а если перепрыгивать нужно через другие джамп-инструкции, которые тоже в свою очередь непонятно какой длины в конечном итоге получатся, то зависимость между длинами джамп-инструкций может стать циркулярной, замкнутой. И тогда в первом подходе мы получаем бесконечную рекурсию, а во втором подходе мы получаем бесконечный цикл на втором проходе (компактификации длинных джампов), потому что компактификатор будет заменять длинный джамп на короткий в одном конкретной месте, а маленькое изменение будет приводить к сдвигу всех (или многих) последующих инструкций на несколько байт назад, сдвиг куска машинного кода на несколько байт переведёт к необходимости пересчёта кучи других смещений, стоящих в разных местах кода, и где-то в результате такого пересчёта величина смещения увеличится настолько, что короткий вариант джамп-инструкции перестанет переходить, а это в свою очередь вызовет необходимость поменять короткий джамп на длинный, а это вызовет новый сдвиг точек/меток, новый пересчёт смещений, то есть это запускает цепную реакцию, конца у которой может не быть.

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

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

Смысл этого опуса в том, что процесс вычисления меток, их взаимного расположения в некоем адресному пространстве, вычисления смещения между ними и текущей точкой ассемблирования/трансляции и подбор нужного варианта кодирования одной и той же инструкции (но с разной длиной, которая в конечном итоге зависит от расположения меток друго относительно друга) — это очень нетривиальный процесс, по сравнению с которым тупое транслирование текстового представления в бинарный машинный код становится супер-тривиальной задачей.

Тогда смысл ассемблера, который вообще ни в курсе ни про одну архитектуру и не знает ни одной мнемоники очень простой — с помощью такого ассемблера по прежнему можно создать код машинный код под любых архитектуру, просто забивая опкоды и кодируя операнды ручками, но при этом вычислять и подставлять адреса и относительные смещения за нас будет инструмент. Нам не придётся от одной правки где-то в середине кода пересчитывать десяток адресов/смещений и менять их в коде. С другой стороны, если такой столь странный инструмент умеет работать с метками и директива, мы можем с помощью такого ассемблера сформировать какой-нибудь не исполняемый, но при этом бинарный файл со сложной структурой. Какой-нибудь PDF, или x509-сертификат, или MBR с таблицей разделов или что-то ещё табличное и с древовидной структурой. Нам не придётся вручную считать смещения, адреса и индексы, за нас это сделает ассемблер.

К слову, наличие таких директив, меток и адресной арифметики с ним даёт нам возможность на выходе ассемблера получить, например, PE-файл или ELF-файл корректного формата даже в том случае, если ассемблер вообще ни сном ни духом ни про PE-формат, ни про ELF-формат и не поддерживает из коробки генерацию исполняемых файлов в таком формате, а может выдавать на выходе только raw-бинарники. Разве что с полем типа CRC/checksum будет проблема — его вычисления распространными директивами типа .org/.align и адресной арифметикой на добьёшься.

Так вот, к чему я об этом всём? Во-первых, как мне показалось, вы мало внимания уделяете обзору именно этой стороны ассемблера: всем этим директивам, константам и трюкам с адресной арифметикой. А это, между прочим, присуще в той или иной степени всем ассемблерам, и с другой стороны, жизнь без именно этих фишек была бы адом — гораздо большим адом, чем если бы int 10h пришлось бы превращать в .db CDh, 10h вручную, вооружившись табличкой. Хотя у вас пример с применением этой адресной арифметики в коде проскальзывает (len = . - msg), но я вном виде об этом в тексте статьи упоминания нигде нет.

Ну а потом, это собственно, то с чего я начал третью претензию. Раз вы не собираетесь делать секрет из того, что помимо собственно трансляции машинных команд ассемблеры понимают ещё и директивы, объявления констант, какие-то ассемблеры понимают макросы, почему вы собственно не пользуетесь этими константами?

Почему mov AH, 9? Почему int 20h? Почему вы не объявляете константы для прерываний, функций и подфункий, не даёте им внятные человеко-читаемые имена? Вам не кажется, что эти 9 и 20h это то, что отталкивает от мысли освоить ассемблер, потому что пугает необходимостью зазубривать все это номера прерываний и функций?

Почему авторы статей базового уровня по ассемблеру x86 так любят реальный режим и DOS?

По-моему, эта комбинация — одна из самых неприятных и отталкивающих. У многих вообще формируется порочная ассоциативная связь: я как-то общался с одним человеком, который работал преподавателем программирования в колледже — когда я сказал, что, в числе прочего, пишу код на ассемблере, он скривил лицо и сказал что-то вроде «фу, ассемблер это же какой-то допотопный язык времен ДОСа». То, что любой современный исполняемый файл состоит из таких же машинных команд, которые можно записать в виде человеко-читаемого ассемблерного листинга, в его картину мира не укладывалось.

Виноваты в этом статьи про ассемблер, которые все дружно начинают знакомство читателя с ассемблером приводя в пример 16-битный реальный режим, 16-битные регистры и прерывания DOS/BIOS.

Информация

В рейтинге
Не участвует
Откуда
Петропавловск, Северо-Казахстанская обл., Казахстан
Зарегистрирован
Активность

Специализация

Десктоп разработчик, Инженер встраиваемых систем
Pure C
Assembler
X86 asm
Win32 API
Visual Basic
MySQL
Git
ООП
Разработка электроники
Обратная разработка