Чем отличается консольное приложение от приложения windows
Какие отличия между Windows и консольными приложениями?
При создании нового проекта в Visual С++ он просит выбрать один из указанных выше.
Единственное отличие заключается в том, что консольное приложение всегда создает консоль, если она не запускается с одного (или консоль активно отключается при запуске). С другой стороны, приложение Windows не создает консоль. Он все равно может прикрепляться к существующей консоли или создавать новый с помощью AllocConsole .
Это делает приложения Windows лучше подходящими для приложений GUI или фоновых приложений, потому что вы обычно не хотите, чтобы для них создано окно терминала.
В более техническом примечании единственная разница между консолью и исполняемым файлом Windows — один байт в PE-заголовке файла exe . Переключение этого байта вручную (например, с использованием шестнадцатеричного редактора) преобразует тип приложения. Это хорошо опубликованный хак, который используется для создания консольных приложений в VB6 (где этот тип приложений явно не поддерживается).
Консольные приложения
Чтобы определить и изменить тип подсистемы приложения, вам необходимо прочитать части заголовка PE. Адрес данных подсистемы не исправлен, поскольку он является частью дополнительного заголовка файла, позиция которого определяется адресом, хранящимся в заголовке файла DOS (в элементе e_lfanew ). Этот адрес фактически указывает на запись _IMAGE_NT_HEADERS , которая, в свою очередь, включает структуру IMAGE_OPTIONAL_HEADER32 . У этого есть член int16 1) называемый Subsystem . Значение члена равно 2 для приложения Windows и 3 для консольного приложения. Существуют и другие подсистемы (в частности, POSIX и ядро). Я написал небольшое приложение VB6 для изменения подсистемы приложения, которое можно загрузить из ActiveVB в качестве исходного кода.
Формат PE не очень хорошо документирован, но этот документ может служить введением: Peering Inside PE: обзор формата исполняемого файла Win32 Portable.
1) Это не противоречит моему утверждению, что только один байт отличается: самый старший байт этого элемента всегда равен 0. Изменяется только младший значащий байт.
![]() |
Основы программирования Каждый профессионал когда-то был чайником. Наверняка вам знакомо состояние, когда “не знаешь как начать думать, чтобы до такого додуматься”. Наверняка вы сталкивались с ситуацией, когда вы просто не знаете, с чего начать. Эта книга ориентирована как раз на таких людей, кто хотел бы стать программистом, но совершенно не знает, как начать этот путь. Подробнее. |
Прежде чем создавать свои компьютерные программы, вы должны знать, какие они бывают и для чего предназначены. Видов программ не так много. Различия между программами можно назвать условными, так как по сути любая программа — это двоичный файл. Но мы так глубоко копать пока не будем, и поговорим о том, чем отличаются программы друг от друга с точки зрения пользователя.
Простейшая консольная программа на Java
Мы уже знаем, какие программы можно создавать в Lazarus. Но сегодня мы будем классифицировать программы с несколько иной точки зрения. Ниже перечислены основные виды программ с краткими описаниями.
Консольные приложения
Консольное приложение — это программа, которая работает с командной строкой. То есть это обычное окно, где пользователь может ввести какую-то команду и получить результат. Здесь нет никаких кнопочек и прочих прелестей Windows.
Пример консольного приложения — это командный интерпретатор, который есть в любой операционной системе. В Windows 95/98/ME — это программа command.com (впрочем, он есть и в более поздних версиях Windows). В Windows 2000 и выше — это программа cmd.exe.
Примеры работы с командной строкой см. в статье Кое что о ДОС.
Оконные приложения
Оконное приложение — это привычная всем программа Windows. То есть это окошко с разными кнопочками и полями для ввода-вывода данных. На сегодняшний день это, пожалуй, самый распространённый вид программ. Именно оконные приложения создают большинство программистов.
Драйверы
Драйвер — это программа, которая обычно служит для “стыковки” компьютерного железа (например, видеокарты) с операционной системой или другой программой. Иногда драйвером называют программу, которая “стыкует” две других программы между собой. Хотя сейчас такие программы принято называть интерфейсами (например, COM-интерфейс или DDE-интерфейс).
Когда я говорю “стыкует”, то я имею ввиду, что драйвер позволяет организовать правильный обмен данными между компьютерным железом и ОС. То есть операционная система обращается не напрямую к железу, а через драйвер.
Зачем так сделано? Дело в том, что производителей, например, видеокарт, существует огромное количество. И все они делают их по своим внутренним стандартам. И операционная система не может знать всё обо всех видеокартах. Поэтому есть определённые общепринятые стандарты, которые поддерживаются операционной системой.
И производитель “железа” делает какую угодно “железяку”, а затем просто пишет программу-драйвер, которая соответствует общепринятым стандартам и “стыкует” эту “железяку” с операционной системой.
Это позволяет извращённым умам делать разные смешные штуки. Например, можно написать “кривой” драйвер, который будет определять USB-мышку как флэшку. Конечно, работать такая “флэшка” не будет, но зато будет прикольно)))
Интерфейсы
Интерфейс — это программа, которая обычно служит для “стыковки” одной программы с другой. Например, вы хотите получить данные из чужой программы. Как это сделать? Если чужая программа поддерживает какой-нибудь стандартный интерфейс (например, DDE), то вы можете использовать этот интерфейс для получения данных из чужой программы.
Библиотеки
Библиотека — это двоичный файл, который хранит разные методы и объекты. Пока это вам ни о чём не говорит. Но просто знайте, что свои процедуры вы можете сохранить в библиотеку, а затем использовать её в других своих программах. Также вы можете использовать в своих программах чужие библиотеки и наоборот — распространять свои библиотеки, чтобы другие программисты могли их использовать в своих программах.
Удобство использования библиотек заключается в том, что они не привязаны к языку программирования. Например, вы можете написать библиотеку на языке С++, а использовать её потом в программах, которые пишите на Паскале.
Резидентные программы
Резидентная программа — это программа, которая работает в фоновом режиме (то есть не видна пользователю и пользователь может о ней даже не подозревать). В фоновом режиме работают, например, антивирусы (и вирусы тоже))).
Системные программы
В общем-то это обычные программы, которые могут быть как консольными, так и оконными приложениями. Сюда же я бы отнёс резидентные программы, библиотеки и драйверы. Пожалуй, это будет не совсем правильно. Однако эта статья для начинающих. Поэтому я не хочу перегружать читателей информацией, которую они пока плохо понимают.
Давайте пока будем думать, что это куча разных вспомогательных программ, которые необходимы для нормальной работы системы и оборудования.
Пока на этом всё. Домашнего задания не будет. Просто подумайте о том, как огромен мир программирования, и сколько всего вам надо будет ещё изучить)))
Я хочу знать, в чем разница между приложением Windows Form, Win32Application и консолью. Я знаю, что приложение Windows Form и приложение Win32 являются графическим инструментом, но я хочу знать, когда использовать одно приложение над другим, и могу ли я преобразовать консольное приложение для окна формы приложения?
Решение
Форма Windows относится к .NET-приложению. Он основан не на собственном Windows API, а на инфраструктуре .NET. Который включает в себя виртуальную машину.
Win32 обычно относится к 32-битному Windows API. Тем не менее _WIN32 макрос определен как для 32-битного, так и для 64-битного программирования. Как тип проекта Visual Studio, он включает в себя программы уровня API с графическим интерфейсом и консольной подсистемой.
Подсистема Windows небольшое целочисленное значение в заголовке исполняемого файла, которое сообщает Windows, какие сервисы нужны этой программе. Это значение может быть проверено, например, с помощью от Microsoft dumpbin программа, например dumpbin c:windows
otepad.exe /headers | find «ubs» , В Windows 9x dumpbin вывод был доступен через функцию предварительного просмотра файла, но эта функция была прекращена.
Каждый процесс в Windows может быть связан с одним и не более чем одним консольным окном.
GUI подсистема означает, что Windows будет НЕ попытайтесь оборудовать каждый экземпляр соответствующим консольным окном. Однако процесс может создать само окно консоли. Обычно эта подсистема используется для обычных программ с графическим пользовательским интерфейсом (следовательно, «GUI»), а для большинства компоновщиков она указывается как «окна».
Консольная подсистема означает, что Windows будет пытаться оснастить каждый экземпляр соответствующим консольным окном, создавая новое при необходимости.
Обратите внимание, что
Тот же самый исходный код может быть собран как консоль или подсистема GUI. И это очень легко сделать. Просто измените спецификацию подсистемы.
Исполняемый файл подсистемы GUI имеет стандартные потоки, как и исполняемый файл консольной подсистемы.
Исполняемый файл консольной подсистемы может представлять графический интерфейс пользователя, так же как и графический интерфейс.
Также обратите внимание, что
- Инструменты Microsoft по умолчанию не принимают стандарт C ++ main для сборки подсистемы GUI. Однако это несоответствующее поведение легко исправить. Просто укажите /entry:mainCRTStartup в настройках компоновщика.
Нет такой проблемы с инструментами GNU, то есть g ++.
Другие решения
- окна формы приложений являются приложениями, которые используют структуры графического интерфейса программирования, такие как .NET, DELPHI или MFC, вместо прямого вызова win32 API.
- С другой стороны, приложение win32 обычно имеет дело непосредственно с Windows API для создания приложений снизу вверх.
- А также консольные приложения нет графического интерфейса Для ввода данных и вывода результатов используется только окно командной строки.
«Приложение Windows Form» — это приложение с графическим интерфейсом .NET.
«Приложение win32» является родным приложением Windows GUI.
«Консольное приложение» — это нативное приложение без графического интерфейса.
Я не совсем понимаю, что именно вы подразумеваете под «преобразованием» одного типа приложения в другое. Но. Если вы говорите об использовании какой-то IDE и конвертируете проект в другой: ДА, это возможно. Главное отличие — это .DLL, с которыми вы связали свое приложение. Например, вы можете настроить проект, открытый как «консоль», чтобы он вел себя как «win32».
Это не очень легко, но остается возможным. Нет, если вы хотите узнать, можно ли преобразовать установленное вами существующее приложение: НЕТ.
Источник: dj-sensor.ru
Виды программ
Прежде чем создавать свои компьютерные программы, вы должны знать, какие они бывают и для чего предназначены. Видов программ не так много. Различия между программами можно назвать условными, так как по сути любая программа — это двоичный файл. Но мы так глубоко копать пока не будем, и поговорим о том, чем отличаются программы друг от друга с точки зрения пользователя.
Мы уже знаем, какие программы можно создавать в Lazarus. Но сегодня мы будем классифицировать программы с несколько иной точки зрения. Ниже перечислены основные виды программ с краткими описаниями.
Консольные приложения
Консольное приложение — это программа, которая работает с командной строкой. То есть это обычное окно, где пользователь может ввести какую-то команду и получить результат. Здесь нет никаких кнопочек и прочих прелестей Windows.
Пример консольного приложения — это командный интерпретатор, который есть в любой операционной системе. В Windows 95/98/ME — это программа command.com (впрочем, он есть и в более поздних версиях Windows). В Windows 2000 и выше — это программа cmd.exe.
Примеры работы с командной строкой см. в статье Кое что о ДОС.
Оконные приложения
Оконное приложение — это привычная всем программа Windows. То есть это окошко с разными кнопочками и полями для ввода-вывода данных. На сегодняшний день это, пожалуй, самый распространённый вид программ. Именно оконные приложения создают большинство программистов.
Драйверы
Драйвер — это программа, которая обычно служит для “стыковки” компьютерного железа (например, видеокарты) с операционной системой или другой программой. Иногда драйвером называют программу, которая “стыкует” две других программы между собой. Хотя сейчас такие программы принято называть интерфейсами (например, COM-интерфейс или DDE-интерфейс).
Когда я говорю “стыкует”, то я имею ввиду, что драйвер позволяет организовать правильный обмен данными между компьютерным железом и ОС. То есть операционная система обращается не напрямую к железу, а через драйвер.
Зачем так сделано? Дело в том, что производителей, например, видеокарт, существует огромное количество. И все они делают их по своим внутренним стандартам. И операционная система не может знать всё обо всех видеокартах. Поэтому есть определённые общепринятые стандарты, которые поддерживаются операционной системой.
И производитель “железа” делает какую угодно “железяку”, а затем просто пишет программу-драйвер, которая соответствует общепринятым стандартам и “стыкует” эту “железяку” с операционной системой.
Это позволяет извращённым умам делать разные смешные штуки. Например, можно написать “кривой” драйвер, который будет определять USB-мышку как флэшку. Конечно, работать такая “флэшка” не будет, но зато будет прикольно)))
Интерфейсы
Интерфейс — это программа, которая обычно служит для “стыковки” одной программы с другой. Например, вы хотите получить данные из чужой программы. Как это сделать? Если чужая программа поддерживает какой-нибудь стандартный интерфейс (например, DDE), то вы можете использовать этот интерфейс для получения данных из чужой программы.
Библиотеки
Библиотека — это двоичный файл, который хранит разные методы и объекты. Пока это вам ни о чём не говорит. Но просто знайте, что свои процедуры вы можете сохранить в библиотеку, а затем использовать её в других своих программах. Также вы можете использовать в своих программах чужие библиотеки и наоборот — распространять свои библиотеки, чтобы другие программисты могли их использовать в своих программах.
Удобство использования библиотек заключается в том, что они не привязаны к языку программирования. Например, вы можете написать библиотеку на языке С++, а использовать её потом в программах, которые пишите на Паскале.
Резидентные программы
Резидентная программа — это программа, которая работает в фоновом режиме (то есть не видна пользователю и пользователь может о ней даже не подозревать). В фоновом режиме работают, например, антивирусы (и вирусы тоже))).
Системные программы
В общем-то это обычные программы, которые могут быть как консольными, так и оконными приложениями. Сюда же я бы отнёс резидентные программы, библиотеки и драйверы. Пожалуй, это будет не совсем правильно. Однако эта статья для начинающих. Поэтому я не хочу перегружать читателей информацией, которую они пока плохо понимают.
Давайте пока будем думать, что это куча разных вспомогательных программ, которые необходимы для нормальной работы системы и оборудования.
Пока на этом всё. Домашнего задания не будет. Просто подумайте о том, как огромен мир программирования, и сколько всего вам надо будет ещё изучить)))
Источник: info-master.su
Консольный ввод-вывод информации с примерами на C# и Windows Terminal
Консоль (Console)- характерная особенность ранних операционных систем (например, MS DOS), использующих интерфейс командной строки для интерактивного обмена информацией с пользователем. Консольные приложения используются и сейчас. По сравнению с графическим интерфейсом, интерфейс командной строки требует меньше системных ресурсов и предоставляет инструменты автоматизации для повторяющихся задач.
Наиболее яркими примерами интерфейсов командной строки (англ. Command line interface, CLI) являются: Командная оболочка Windows, PowerShell, а также Bash, доступная на всех платформах (наибольшее распространение Bash получил в Unix-системах и Mac, присутствует также в компонентах Подсистема Windows для Linux (англ. Windows Subsystem for Linux, WSL)).
В операционной системе Windows консоль называется окном командной строки, для вызова которой Вы можете пройти в меню Пуск — Командная строка. В 2019 году компания Micrsoft также представила Windows Terminal — современное консольное приложение для пользователей инструментов и оболочек командной строки, таких как Command Prompt, PowerShell и WSL.
Форма интерфейсов командной строки используется в основном для обработки сценариев команд с использованием последовательности операций чтения (для принятия данных путем ввода текстовой информации пользователем) и операций записи (в вывод консоли, для отображения результатов обратной связи).
На платформе Microsoft .Net Core такие операции в виде методов описаны в классе System.Console, предоставляющем базовую поддержку для приложений, считывающих и записывающих символы в консоль стандартных входных и выходных потоков.
Программный способ записи в вывод консоли
Для вывода информации на консоль применяются 2 ключевых метода класса Console — Console.WriteLine и Console.Write, отличие которых заключается в том, что WriteLine самостоятельно добавляет терминатор строки (разделитель строки) ко всему, что вы записали. Использование метода Write предполагает ручное разбиение строки на несколько путем добавления в запись вывода терминатора строки, где это необходимо.
Записывает указанные данные с текущим признаком конца строки в стандартный выходной поток.
Console.WriteLine(“Hello, World!”);
Console.WriteLine(“=Second Line=”);
Результатом вывода будет две строки.
Записывает текстовое представление заданного значения или значений в стандартный выходной поток без признака конца строки.
Console.Write(“Hello,”);
Console.Write(“ “);
Console.Write(“World!”);
Console.Write(Environment.NewLine);
Console.Write(“=Second Line=”);
Результат вывода этого примера идентичен.
Во втором примере для разделения строки используется в качестве терминатора свойство Environment.NewLine — это обеспечивает эффективный способ выбора комбинации символов конца строки, которая зависит от используемой вами платформы. Так, большинство Unix-систем применяет специальную комбинацию “n” в качестве терминатора строки, тогда как Windows-системы “rn”.
Формирование строк
При выводе информации вы можете использовать операторы + или += , интерполяцию строк и иные методы для объединения и формирования строковых переменных.
Программный способ ввода информации с консоли
Как и запись в вывод, класс Console предоставляет различные методы для чтения ввода от пользователя.
Метод Console.ReadLine
Считывает набор символов до тех пор, пока не найдет признак окончания или новой строки, и возвращает все, что он считает, как строковое значение.
Console.WriteLine(“What is your name?”);
string response = Console.ReadLine();
Console.WriteLine(“Hello, “ + response+“!“);
Метод Console.Read
Считывает следующий символ в строке и возвращает его как код целочисленного символа.
Console.WriteLine(“Please, type anything: “);
int value = Console.Read();
Console.Write(“You typed: “ + (char)value);
В отличие от ReadLine , метод Read возвращает по одному символу за раз, пока не достигнет конца ввода. Read фактически не возвращает строку, он возвращает целое число, представление ASCII введенного символа. В примере используется функция char() для преобразования полученного целого числа, чтобы получить действительно введенный символ.
Метод Console.ReadKey
Считывает следующий символ в строке и возвращает его как экземпляр ConsoleKeyInfo.
ConsoleKeyInfo описывает нажатую клавишу, включая символ, представленный этой клавишей, и состояние управляющих клавиш-модификаторов (например, Shift, Alt и др.). Нижеследующий пример ожидает нажатия клавиши Enter.
while (Console.ReadKey().Key != ConsoleKey.Enter) <>
Источник: medium.com
Что такое консольная программа это
Рустэм Галеев aka Roustem
5. Консольное приложение
Существует разновидность приложений Windows, которые называются консольными. По своим «внешним» проявлениям они напоминают приложения DOS, запущенные в Windows. Тем не менее, это настоящие Win32-приложения, которые под DOS работать не будут; для них также доступен Win32 API, а кроме того, они могут использовать консоль — окно, предоставляемое системой, которое работает в текстовом режиме и в которое можно вводить данные с клавиатуры.
Особенность консольных приложений в том, что они работают не в графическом, а в текстовом режиме. Для этого используются унаследованые от DOS так называемые стандартный ввод и стандартный вывод. Все, что пользователь вводит с клавиатуры (когда консольное окно имеет фокус), попадает в буфер стандартного ввода, откуда данные можно читать, как из файла. Выходные же данные можно записать, как в файл, в буфер стандартного вывода, и они будут отображены в консольном окне.
Еще одной особенностью стандартных ввода и вывода является возможность их перенаправления в файл. Этим мы уже пользовались при создании наших приложений, используя в командной строке знаки ‘gt’ для перенаправления вывода. Debug, будучи приложением DOS, использует для приема команд стандартный ввод, а для отображения данных — стандартный вывод. Когда мы писали: ‘debug gt result.lst’ вся информация, которая выводилась бы на экран, попадала в файл ‘result.lst’.
Из приложений, подобных debug, т.е. использующих для ввода данных стандартный ввод, а для вывода — соответственно стандартный вывод, можно строить даже своеобразные «конвейеры». Для этого данные из стандартного вывода одного приложения подают на стандартный ввод другого посредством специального знака командной строки ‘|’ (вертикальной черты). В команде
приложение1 | приложение 2 | приложение3
данные проходят последовательную обработку этими тремя приложениями. Например, приложение1 могло бы составлять список из данных источника, приложение2 — сортировать его, а приложение3 — форматировать нужным образом. Таким образом, у консольных приложений есть свои преимущества; их удобно использовать в качестве «строительных блоков» для автоматизации многих рутинных задач, не требующих интерактивного взаимодействия с пользователем.
Работа со стандартными вводом и выводом «изнутри» подобна работе с файлами. Стандартный ввод выглядит как файл с разрешением «только для чтения», а стандартный вывод — как файл с разрешением «только для записи». Для работы с ними используют соответствующие функции API — ReadFile и WriteFile из модуля Kernel32.dll. Рассмотрим их подробнее.
При вызове функции ReadFile в стек помещаются 5 параметров в следующем порядке:
- адрес структуры, использующейся при асинхронном вводе-выводе. При обычном (синхронном) вводе значение этого параметра равно нулю;
- адрес переменной (4 байта), по которому будет записано количество действительно прочитанных функцией байтов (это значение может быть меньше заявленного — например, если кончились данные);
- число байтов, которые нужно прочесть («заявка»);
- адрес, по которому нужно разместить прочитанные данные (буфер);
- описатель файла, из которого производится чтение.
Внимания заслуживает последний аргумент. Для работы с файлом используется так называемый описатель (handle) — это некий идентификатор, который система присваивает файлу после его открытия. На самом деле, при открытии файла создается внутренняя системная структура, в которой хранятся различные вспомогательные данные, такие как текущая позиция, с которой нужно читать или записывать данные, и т.п. Все обращения к файлам возможны только после их открытия и только по их описателям.
Функция WriteFile также принимает 5 схожих параметров:
- адрес структуры для асинхронного вывода;
- адрес переменной (4 байта), в которую будет помещено количество действительно записанных байтов;
- число байтов, которые нужно записать («заявка»);
- адрес начала буфера, где находятся предназначенные для записи данные;
- описатель файла, в который производится запись.
Но как получить нужные описатели? В случае файлов существуют специальные функции API (наподобие CreateFile) для их открытия. Для стандартного ввода-вывода тоже существует своя функция — GetStdHandle, тоже из модуля Kernel32.dll. Она принимает лишь один аргумент — число, указывающее на тип нужного описателя (для стандартного ввода или вывода).
На самом деле, существует еще и третий тип — стандартная ошибка, он, как и стандартный вывод, служит для отображения сообщений на экране. Его можно использовать в тех случаях, когда нужно как-то разделить обычные сообщения и сообщения об ошибках (например, можно использовать перенаправление только для стандартной ошибки — тогда эти сообщения будут записаны в файл и не попадут на экран). В качестве параметра функции GetStdHandle используются 0FFFFFFF6h для стандартного ввода, 0FFFFFFF5h для стандартного вывода и 0FFFFFFF4h для стандартной ошибки.
Настало время обсудить один важный вопрос. Функции не только принимают параметры, часто они еще возвращают значения. Результат работы функции по ее возвращении (т.е. перед выполнением следующей после вызова функции инструкции) оказывается в регистре EAX.
Это общее соглашение: когда мы начнем создавать свои функции, мы тоже будем должны записывать в регистр EAX значение, которое должно быть возвращено как результат функции. В случае функции GetStdHandle таким результатом как раз и является нужный нам описатель. Его можно либо сохранить где-то в памяти (переписав туда значение из регистра), либо использовать прямо в регистре, если вызов нужной функции непосредственно следует после получения описателя.
Здесь нужно отметить еще один момент. Мы уже знаем, что из 8 общих регистров один (ESP) используется в качестве указателя стека, и его трогать нельзя.
На самом деле, при работе со стеком используется еще и второй регистр — EBP, поэтому число доступных для манипуляций регистров сокращается до 6. Теперь задумайтесь над вопросом: а что случается с данными, которые находились в регистрах, после вызова функции? Особенно, если это «чужие» функции, являющиеся для нас «черными ящиками» (как в случае с функциями API).
Значения в регистрах могут быть перезаписаны (ведь надо с чем-то работать!), а могут остаться без изменения. Чтобы внести ясность в этот вопрос, для работы с функциями Win32 API было принято следующее соглашение: при вызовах любых функций значения регистров EBX, ESI и EDI остаются без изменений — какие были перед вызовом функции, такие будут и после; значения же регистров EAX, ECX и EDX могут быть изменены произвольным образом. В регистре EAX, как мы уже знаем, будет находиться результат работы функции (если функция возвращает результат). Если функция не имеет возвращаемого результата, значение в EAX не определено.
Практический же вывод такой. Если нам нужно, чтобы значение в «изменяемых» регистрах (EAX, ECX или EDX) сохранилось после вызова функции, перед ее вызовом необходимо поместить значение соответствующего регистра в стек, а после вызова функции — извлечь его оттуда (в тот же регистр). И наоборот: если мы создаем свою функцию, которую может вызвать система (например, забегая вперед, это относится к главной функции окна), и если в работе этой функции нам приходится использовать регистры, которые не должны изменяться (EBX, ESI или EDI), мы должны в самом начале функции сохранить значение этого используемого регистра в стеке, а перед возвратом из функции — восстановить его. В случае «изменяемых» регистров этого делать не нужно.
Что ж, необходимый теоретический минимум мы прошли; теперь можно применить его на практике. Попробуем создать простое консольное приложение, выводящее сообщение. «Макет» нашего приложения будет следующий: сначала идет PE-заголовок, затем секции кода (.code), данных (.data) и вспомогательных данных для импорта (.rdata). По сравнению с прошлым разом для разнообразия переставлены местами секции .data и .rdata. Как и раньше, секции располагаются в памяти по смещениям 1000h, 2000h и 3000h соответственно, а в файле — 200h, 400h и 600h.
Начнем с секции данных. Их немного — в начале секции (по смещению 2000h, который после загрузки превратится в виртуальный адрес 402000h) разместим переменную в 4 байта для вывода количества записанных байтов. Сразу за ней (по адресу 402004h) будет выводимая текстовая строка. Набираем файл data.txt:
n data.bin r cx 200 f 0 l 200 0 a 0 ; 4 байта для числа выведенных байтов db 0 0 0 0 ; выводимая строка db «Greetings from console window» 0a 0d 0 m 0 l 200 100 w q
Числа 0Ah и 0Dh после строки являются ASCII-символами перехода на новую строку; сама строка должна завершаться нулем.
Теперь надо заняться секцией импорта. Нам нужно импортировать три функции, и все из модуля Kernel32.dll: GetStdHandle, WriteFile и ExitProcess. В начале секции, как обычно, таблица импортируемых адресов (IAT); на этот раз она имеет, по числу функций, 3 поля и четвертое нулевое. Сразу вслед за IAT расположим таблицу поиска, тем более, что они при загрузке должны быть идентичны.
Затем будет таблица импорта, содержащая одну запись для единственного импортируемого модуля и одну завершающую нулевую запись (общий размер 28h байт). Затем последуют строки с именами модуля и функций. Здесь удобно использовать два прохода в режиме ассемблирования — при «черновом» содержимое структур можно просто заполнять нулями (сохраняя лишь размер полей), а для второго «чистового» прохода подставить из полученного файла отчета нужные значения. Для этой же цели лучше выбрать для «сборки» образа в debug то же смещение, что и у загруженной в память секции (в данном случае — 3000h; файл rdata.txt):
n rdata.bin r cx 200 f 3000 l 200 0 a 3000 ; Таблица импортируемых адресов: до загрузки ; идентична таблице поиска ; будущий адрес GetStdHandle db 55 30 0 0 ; будущий адрес WriteFile db 64 30 0 0 ; будущий адрес ExitProcess db 70 30 0 0 ; завершение таблицы нулями db 0 0 0 0 ; Таблица поиска ; смещение строки с именем GetStdHandle db 55 30 0 0 ; смещение строки с именем WriteFile db 64 30 0 0 ; смещение строки с именем ExitProcess db 70 30 0 0 ; завершающие нули db 0 0 0 0 ; Таблица импорта ; строка для импорта из Kernel32.dll: ; смещение таблицы поиска db 10 30 0 0 ; 2 пустых поля db 0 0 0 0 0 0 0 0 ; смещение имени модуля db 48 30 0 0 ; смещение таблицы импортируемых адресов db 0 30 0 0 ; завершение таблицы — пустая строка (20 нулевых байтов) db 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ; Имя импортируемого модуля db «Kernel32.dll» 0 ; Имена импортируемых функций; вместо «подсказок» нули db 0 0 «GetStdHandle» 0 db 0 0 «WriteFile» 0 db 0 0 «ExitProcess» 0 m 3000 l 200 100 w q
Переходим к секции кода.
Освежим знания по инструкциям: «короткая» команда помещения в стек 6Ah + 1 байт, «длинная» — 68h + 4 байта; команда вызова функции — опкод 0FFh, байт ModR/M 15h, указывающий, что операнд находится в памяти по 4-байтному адресу, который включен в инструкцию. Обратите внимание, что для помещения в стек числа 0FFFFFFF5h мы можем использовать «короткий» вариант инструкции со знаковым расширением, т.к. это на самом деле отрицательное число, представимое в виде 1 байта (-11 = 0F5h. Функцию WriteFile мы вызываем непосредственно после функции GetStdHandle, значит, нужный нам описатель файла будет находиться в регистре EAX; поэтому в этот раз придется использовать также инструкцию помещения в стек значения регистра EAX (если помните, это 50h). Указатели на нужные нам функции будут находиться после загрузки в соответствующих полях IAT, по адресам 403000h, 403004h и 403008h. Итак, файл code.txt:
n code.bin r cx 200 f 0 l 200 0 a 0 ; параметр для стандартного вывода db 6a f5 ; вызов GetStdHandle db ff 15 0 30 40 0 ; параметры для WriteFile: ; не используется — 0 db 6a 0 ; адрес переменной для числа выведенных символов db 68 0 20 40 0 ; длина строки = 30 (1Eh) db 6a 1e ; адрес выводимой строки db 68 4 20 40 0 ; содержимое регистра EAX db 50 ; вызов WriteFile db ff 15 4 30 40 0 ; параметр кода завершения (0) db 6a 0 ; вызов ExitProcess db ff 15 8 30 40 0 m 0 l 200 100 w q
Осталось лишь добавить PE-заголовок.
Сначала скопируем его шаблон (header.txt) в рабочий каталог, а затем слегка его подправим. Потребуются изменения всего в трех местах. Самое главное — нужно изменить подсистему: вместо графической (2) поставить консольную (3). Собственно, это единственное, чем консольные приложения отличаются от графических! Находим в шаблоне строки:
a 9C ; Подсистема: 2 — графическая, 03 — консольная (2 байта)
Сразу после нее должно быть:
db 03 00
Теперь надо указать расположение таблицы импорта. Находим строку:
; Здесь начинается первый элемент каталога:
За ней должен следовать текст:
; смещение таблицы экспорта (4 байта) db 0 0 0 0 ; размер таблицы экспорта (4 байта) db 0 0 0 0 ; Второй элемент каталога: ; смещение таблицы импорта (4 байта) db 20 30 0 0 ; размер таблицы импорта (4 байта) db 28 0 0 0
Наконец, мы поменяли местами секции .data и .rdata (хотя в принципе этого можно было и не делать). Находим начало второй секции:
; вторая секция
И заменяем оставшийся текст на следующий:
db ‘.data’ 0 0 0 db 0 2 0 0 db 0 20 0 0 db 0 2 0 0 db 0 4 0 0 db 0 0 0 0 0 0 0 0 0 0 0 0 db 40 0 0 c0 ; ; третья секция db ‘.rdata’ 0 0 db 0 2 0 0 db 0 30 0 0 db 0 2 0 0 db 0 6 0 0 db 0 0 0 0 0 0 0 0 0 0 0 0 db 40 0 0 40 m 0 l 200 100 w q
Вот и все. В файле сборки (make.bat) секции также должны идти в соответствующем порядке:
Проверив файл отчета report.lst, можно запускать cnsl.exe. Если вы запускаете его не из консоли, создаваемое окно будет мелькать — закрываться сразу после завершения программы. Поэтому можно запустить сначала консоль командной строки DOS и уже из него — наше приложение, набрав его имя (и путь, если требуется).
Еще одно примечание — в консольных приложениях используется кодировка DOS. Поэтому если вы набрали текст для вывода в Блокноте и на русском, то в консольном окне прочесть его не сможете — в Windows используется другая кодировка (ANSI).
На самом деле, возможности текстового вывода шире, чем можно было бы подумать. Попробуйте в качестве примера использовать такой файл data.txt:
n data1.bin r cx 200 f 0 l 200 0 a 0 db 0 0 0 0 db c9 cd cd cb cd cd bb 0a 0d db ba 20 20 ba 20 20 ba 0a 0d db c7 c4 c4 d7 c4 c4 b6 0a 0d db ba 20 20 ba 20 20 ba 0a 0d db c8 cd cd ca cd cd bc 0a 0d 0 m 0 l 200 100 w q
Чтобы пример работал правильно, надо еще подправить кодовую секцию — там, где строка:
; длина строки = 30 (1Eh)
Источник: citforum.ru