Собственно вот ЭТО: Код (Text): format PE console 4.0 include 'win32ax.inc' struct COORD x dw ? y dw ? ends struct SMALL_RECT Left dw ? Top dw ? Right dw ? Bottom dw ? ends struct CONSOLE_SCREEN_BUFFER_INFO dwSize COORD <> dwCursorPosition COORD <> wAttributes dw ? srWindow SMALL_RECT <> dwMaximumWindowSize COORD <> ends entry start ;********************************************************************************************************************** section '.text' code readable executable start: invoke GetStdHandle, STD_OUTPUT_HANDLE ; Получаем хэндл окна вывода mov [outputHandle], eax ; Сохраняем invoke GetStdHandle, STD_INPUT_HANDLE mov [inputHandle], eax invoke VirtualAlloc, 0, 4096, MEM_COMMIT, PAGE_READWRITE ; Резервируем память под вводимую строку mov [buffer], eax .if eax = 0 stdcall WriteLnStringToConsole, 'Error allocation memory' jmp lExit .endif stdcall WriteLnStringToConsole, 'Hello' .while TRUE stdcall WriteStringToConsole, '-' stdcall ReadStringFromConsole, [buffer], 512 ; Выход invoke lstrcmp, dword[buffer], comExit .if eax = 0 jmp lExit .endif ; Показать помощь invoke lstrcmp, dword[buffer], comHelp .if eax = 0 stdcall WriteLnStringToConsole, help1 .endif ; Загрузка файла mov eax, [buffer] .if byte[eax] = 'l' stdcall LoadFile .endif .endw lExit: ; Возвращаем память системе .if dword[buffer] <> 0 invoke VirtualFree, [buffer], 4096, MEM_DECOMMIT .endif invoke ExitProcess, 0 proc LoadFile uses eax ebx esi invoke VirtualAlloc, 0, 512, MEM_COMMIT, PAGE_READWRITE ; Резервируем место под заголовок PE mov [bufferPE], eax mov eax, [buffer] add eax, 2 invoke CreateFile, eax, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL mov [handleFile], eax .if eax = INVALID_HANDLE_VALUE stdcall WriteLnStringToConsole, 'File not exit or accses denied' ret .endif invoke ReadFile, eax, [bufferPE], 512, temp, NULL ; Переходим к выводу содержимого PE построчно mov eax, 0 mov esi, [bufferPE] .while eax < 16 stdcall WriteToConsole16Bytes, esi add esi, 16 add eax, 1 .endw ; Выводим содержимое EXEHEADER (00h...19h) stdcall WriteLnStringToConsole, 'EXEHEADER:' mov esi, [bufferPE] mov ebx, @tableExeHeader - 1 ; В ebx теперь адрес таблицы для вывода mov al, 0 .while TRUE ; Выбираем символы до 0ffh .while al <> 0ffh add ebx, 1 mov al, byte[ebx] .endw .if byte[ebx+1] = 0ffh jmp _exitCycle .endif add ebx, 2 stdcall WriteStringToConsole, ebx stdcall SetXPosCursor, 19 movzx eax, byte[ebx-1] stdcall WriteWordToConsole, dword[esi+eax] .endw _exitCycle: invoke VirtualFree, [bufferPE], 512, MEM_DECOMMIT ret @tableExeHeader db 0ffh, 00h, 'Signature ', 0 db 0ffh, 02h, 'Extra bytes ', 0 db 0ffh, 04h, 'Pages ', 0 db 0ffh, 06h, 'Relocation items ', 0 db 0ffh, 08h, 'Header size ', 0 db 0ffh, 0ah, 'Minimum allocation ', 0 db 0ffh, 0ch, 'Maximum allocation ', 0 db 0ffh, 0eh, 'Initialization SS ', 0 db 0ffh, 10h, 'Initialization SP ', 0 db 0ffh, 12h, 'Check Sum ', 0 db 0ffh, 14h, 'Initialization IP ', 0 db 0ffh, 16h, 'Initialization CS ', 0 db 0ffh, 18h, 'Relocation table ', 0 db 0ffh, 1ah, 'Overaly ', 0 db 0ffh, 1ch, 'Reserved ', 0 db 0ffh, 0ffh ; Признак конца таблицы endp ; Установить горизонтальную позицию курсора proc SetXPosCursor uses eax, xPos:WORD local info:CONSOLE_SCREEN_BUFFER_INFO invoke GetConsoleScreenBufferInfo, [outputHandle], addr info mov ax, [xPos] mov [info.dwCursorPosition.x], ax lea eax, [info.dwCursorPosition] invoke SetConsoleCursorPosition, [outputHandle], dword[eax]; ret endp ; Процедура выводит в консоль 16-битное значение proc WriteWordToConsole uses eax ebx edi, value local buffer[6]:BYTE ; Буфер для полученной строки. 4 символа - столько занимает одно слово на экране lea edi, [buffer] ; В edi адрес буфера для вывода mov ebx, [value] mov al, bh ; В AL старший байт stdcall ByteToString ; Преобразуем байт в строку mov word[edi], ax ; Сохраняем текстовое значене старшего байта mov al, bl stdcall ByteToString mov word[edi+2], ax mov word[edi+4], 0a0dh invoke WriteConsole, [outputHandle], addr buffer, 6, temp, NULL ret endp ; Выводит строку в консоль. Пример вызова: stdcall WriteStringToConsole, 'String' proc WriteStringToConsole uses eax, addressString stdcall LenString, [addressString] invoke WriteConsole, [outputHandle], [addressString], eax, temp, NULL ret endp ; Выводит строку консоль и переводить курсор на следующую строку. Пример вызова: stdcall WriteLnStringToConsole, 'String' proc WriteLnStringToConsole, addressString stdcall WriteStringToConsole, [addressString] invoke WriteConsole, [outputHandle], @bufferEnter, 2, temp, NULL ret @bufferEnter db 0dh, 0ah endp ; Находит длину строки proc LenString uses esi, addressString mov esi, [addressString] ; В esi теперь адрес строки, длину которой нужно вычислить mov eax, 0 .while byte[esi+eax] <> 0 add eax, 1 .endw ret endp ; Процедура TetraToChar преобразует младщую тетраду регистра al в отображаемый 16-тиричный символ и возвращает в регистре al proc TetraToChar uses ebx and al, 0fh ; Обнуляем старшую тетраду mov ebx, @table xlat [ebx] ret @table db '0', '1', '2', '3', '4' ,'5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' endp ; Процедура ByteToString преобразует содержимое регистра al в текстовое отображение (два символа) и возвращает их в регистре ax proc ByteToString uses ebx ecx mov bl, al ; Сохраняем содержимое регистра al в bl ; Преобразуем старшую тетраду shr al, 4 ; Для чего сдвигаем старшую тетраду в младшую call TetraToChar ; И преобразуем в символ mov ch, al ; И сохраняем в старшем байте cx ; Теперь преобразуем младшую тетраду mov al, bl call TetraToChar mov cl, al ; И сохраняем в младшем байте cx mov ax, cx ; Возвращаем результат xchg al, ah ret endp ; Процедура WriteToConsole16Bytes выводит в консоль 16 байт, начиная с startAddress proc WriteToConsole16Bytes uses eax ecx esi edi, startAddress local buffer[50]:BYTE mov ecx, 0 mov esi, [startAddress] ; Теперь в esi находится адрес первого байта lea edi, [buffer] ; А в edi адрес итоговой строки - той, которую будем выводить на экран .while ecx < 16 mov al, [esi] ; Загружаем очередной байт в al call ByteToString ; Преобразуем его в текстовый вид mov word[edi], ax ; Сохраняем в буфере для вывода add edi, 2 mov byte[edi], ' ' ; И записываем за ним пробел add edi, 1 ; Переходим к add esi, 1 ; следующему байту add ecx, 1 .endw invoke WriteConsole, [outputHandle], addr buffer, 50, temp, NULL ; А теперь выводим в текстовом виде mov esi, [startAddress] lea edi, [buffer] mov ecx, 0 .while ecx < 16 mov al, byte[esi] .if al < ' ' mov al, '.' .elseif al = 255 mov al, '.' .endif mov byte[edi], al add esi, 1 add edi, 1 add ecx, 1 .endw mov byte[edi], 0dh add edi, 1 mov byte[edi], 0ah invoke WriteConsole, [outputHandle], addr buffer, 18, temp, NULL ret endp ; Процедура ReadStringFromConsole читает строку символов с консоли и сохраняет её по заданному адресу. numBytes - максимальное число вводимых символов ; Символ ввода заменяется 0 - текстовая строка превращается в С++-строку proc ReadStringFromConsole uses esi, address, numBytes local realBytes:DWORD invoke ReadConsole, [inputHandle], [address], [numBytes], addr realBytes, 0 ; Читаем команду с консоли mov esi, [address] ; В esi адрес первого байта полученной строки sub esi, 1 .repeat add esi, 1 .until byte[esi] = 0dh mov byte[esi], 0 ; И заменяем его на 0 ret endp ;********************************************************************************************** section '.data' data readable writeable temp dd ? outputHandle dd ? inputHandle dd ? buffer dd 0 ; Здесь указатель на выделяемый буфер под вводимую строку bufferPE dd 0 ; Здесь будет указатель на область памяти, в которой будет заголовок PE handleFile dd 0 ; Дальше строки. Некоторые даже с длинами ; Это строки главного меню comHelp db '?', 0 comLoadFile db 'l', 0 comExit db 'q', 0 ; Это строки, которые выводятся по меню "Help" help1 db 'help ? - this screen', 0dh, 0ah help2 db 'load <file> l - loading <file>', 0dh, 0ah help3 db 'quit q - not comments', 0 ;*************************************************************************************************** section '.idata' import data readable writeable library kernel, 'KERNEL32.DLL' import kernel,\ ExitProcess, 'ExitProcess',\ GetStdHandle, 'GetStdHandle',\ WriteConsole, 'WriteConsoleA',\ ReadConsole, 'ReadConsoleA',\ lstrcmp, 'lstrcmp', \ VirtualAlloc, 'VirtualAlloc',\ VirtualFree, 'VirtualFree',\ CreateFile, 'CreateFileA',\ ReadFile, 'ReadFile',\ GetConsoleScreenBufferInfo, 'GetConsoleScreenBufferInfo',\ SetConsoleCursorPosition, 'SetConsoleCursorPosition' Прошу совета знающих людей. Посмотрите код, пожалуйста, скажите, достаточно ли он читабелен, что в нём стоит упростить, что наоборот, усложнить. Самому не очень нравится. Хотелось бы побольше всяких советов)
Главным критерием всегда был и остается правильность/работоспособность, это мое имхо. Если все работает, то зачем нужно менять, улучшать?
х.з. не понимаю зачем писать на асме .if .while и пр. пиши уж тогда на си - он хоть оптимизировать будет твои макросы. это моё субъективное имхо: если асм - то чистый, безо всяких там .if, если нужно писать большой проект и сделать его читабельным, то пиши на сях.
cupuyc как можно не согласиться да и printf решает. А сам код очень даже стройный, вроде с первого взгляда все видно.
да я не про то, что проще, а про то, как правильнее имхо. мой субъективный взгляд говорит мне, что правильнее подобные задачи решать на сях. если же решать их на асме то без использования всяких ифов и пр. в противном случае я не вижу толку от использования асма. могу и ошибаться, не спорю.
JCronuz Это верно лишь отчасти. На работе одна чувиха тоже так говорит. Потом позвала меня : "Посмотри, где у меня ошибка". Я глянул, а там - button1, var1, никакого форматирования, бегины не соответсвтуют ендам. Совершенно никакой системы в написании. Я ей честно сказал, что копаться в ЭТОМ у меня нет никакого желания. Она мне в ответ сказала "Главное - чтоб работало". Естественно было ответить - "Ну так у тебя и не работает". Но я сдержался. За это мне +1. А суть моего вопроса в том, чтобы код сделать как можно более читабельным. Например, в первом варианте этой программы не было подпрограммы для вывода строки. Из главной функции вызывалась функция WriteConsole и туда передавались параметры. Для улучшения наглядности была сделана WriteStringToConsole с одним параметром. Так проще код читается, никто не будет спорить. Далее. Вначате не было условных операторов типа .if. В итоге по всему коду были разбросаны метки. От этого он закакался и выглядел сложным для восприятия. После добавления высокоуровневых конструкций он стал более простым. Да, после этого код стал не чисто ассемблерным, и, как показал отладчик, генерируемые fasm-ом инструкции не совершненны. Но код перестал быть чисто ассемблерным, когда я стал в нём применять invoke, а наглядность получаемого покрывает второй недостаток. MSoft Спасибо) cupuyc Основная причина - код выглядит очень сложным. Полностью согласен. Лучше писать на том, на чем будет быстрее. Если брать мой случай, то я пишу не для практических целей, а скорее для учебных. Умею и без ифов, значит, могу их использовать. Если подскажешь, что можно простого написать кроме просмотрщика заголовка исполняемого файла, буду весьма мерси. А вообще, акромЯ первых двух ответов, ничего позитивного. Вопрос был в том, как сделать данный код более совершенным с точки зрения чтения. Ёлы-палы, никто даже не поинтересовался, почему у меня вместо inc add используется. Вопрос открыт. Если кто-то увидит, что здесь можно улучшить, буду весьма признателен.
Phuntik так ты скажи ЗАЧЕМ? я бы посмотрел, а так тут ответ просто переходи на си, а если для учебных целей, то почему add вместо inc?
Глянул еще раз, ну все понятно, но и код достаточно простой ведь. Удачная структура ( ну на первый взгляд хотя бы) tableExeHeader
что-то я начинаю сомневаться вообще во всём. какого лешего было вводить инструкцию inc, если есть add и она быстрее? вобщем я асма не знаю вообще (((
В юзермоде не имеет значения сложение или инкремент, ибо планирование выполняется, темболее что не миллионы итераций. Вобщем код оформлен нормально. Из недостатков - сразу заметил не приятные сравнения регистров с нулём.
Phuntik На мой взгляд в программе излишнее злоупотребление функцией VirtualAlloc, потому что выделяются очень небольшие буферы фиксированной длины. Лучше зарезервировать для них место в конце секции данных. Очень неэффективно написаны функции XXXToString. Лучше написать одну функцию которая принимает в eax число, а в ecx сколько цифр выводить, а вместо XXXToString написать макросы.
SPA Чем проще разобраться в коде, тем проще над ним работать. Вот и стремлюсь к совершенству. Конечно, это приходит с опытом, но также я не против поучиться у других. add вместо inc действительно, потому как считал из какого-то источника, что он быстрее. Хотя сомнительно весьма - копилятор VS никогда не делает add 1, а всегда inc. А что касается того, что inc нагляднее, то есть такое правило - "Преждевременная оптимизация - это плохо, но какие-то вещи нужно оптимизировать автоматически". Например, в этом случае: for(int i = 0; i < widht(); i++) { } нужно писать так: int w = widht(); for(int i = 0; i < w; i++) { } Black_mirror Да, в первой версии динамического выделения памяти не было, добавил его позднее, так как не совсем правильным показалось, что данные занимают на порядок больше места, чем код). Потом, очевидно, уберу. Да, это достаточно тёмный момент. Понятно, что надо улучшать. Я и сам в них путаюсь. В-общем, будем считать, что код достаточно хорошо и читабельно написан. Я и обратился потому, что не так красиво, как на С, получается. Но нагляднее, наверное, и не получится)
Качество кода можно оценить по количеству "what the fuck?" в минуту. Поехали: - outputHandle и другие глобальные переменные зачем нужны? уж точно не файловый хендл. - что из себя представляет конструкции .if eax = 0 : jmp lExit - это 2 джампа подряд? - что еще делает LoadFile кроме чтения файла, вывод на консоль что ли? ) кстати почитай про file mapping и SEC_IMAGE. - смешивать код и данные плохо. если есть необходимость, располагай ud2 после ret, по рекомендации Интел.
J0E О! Серьёзный подход. И сразу видно, что всё не так уж хорошо. Что ж, рассмотрим по порядку. Насчёт outputHandle(хэндл консоли) уж точно. Лучше бы было всё это запрятать в одну функцию вывода в консоль и там уж руководить. По мере разрастания программы это и произойдёт, видимо. Вообще, в программе на ассемблере глобальные переменные - это совсем неплохо, разве не так? По крайней мере, пока их не много и не составляет труда следить за ними. Остальные глобальные переменные - строки для вывода в основном. И где ж их хранить? Ну уж файловый хэндл не набо было оставлять на виду, это точно. Ну, сам компилятор совсем не здорово генерирует команды для .if. Ничего страшного, если я ещё добавил неоптимальности. Оптимальность принесена в жертву читабельности, что в данном случае вполне оправданно. Если этот участок будет выполняться в цикле и нужна будет скорость, я, скорее всего, перепишу его без использования .if Да, вывод на консоль. Название неудачное, согласен. Поподробнее можно? Данные я располагаю или в стеке, или после ret. Что касается передачи строк в функции, то действительно, очень страшный код в отладчике получается, но я не знаю другого способа, кроме как вручную размещать строки в памяти и передавать их адреса, что жутко неудобно. Если знаешь третий способ, то поделись. И что такое ud2, и поделись ссылочкой на рекомендацию Интел, пожалуйста, можно просто номер главы и тома)