Пирогов Владислав Юрьевич

О программировании, ИТ и обо всем по немногу

Из цикла Письма
pirogov_vju
Как и обещал, начинаю публиковать некоторые письма ко мне и мои ответы.

Письмо.

Добрый день, уважаемый Владислав Юрьевич!

Пишет вам читатель Вашей книги"Ассемблер для Windows".
И конечно, по ходу чтения возникают достаточно часто вопросы по существу.
В частности,очень актуален ля меня сейчас вопрос о том,
каким образом пои записи в уже созданнйй текстовой файл( с помощью ф-ции"CreateFile")
можно сделать так,чтобы строка текста,которая в этот текстовой файл записывается,
т.е. используя ф-цию"WriteFile",где в качестве второго параметра передаётся адрес строки с текстом,
который и требуется вписать в наш текстовой ф-л( в моём примере он именуется как"D:\NewFile.txt",
т.е. находится в корневом каталоге на диске"D:\"),записывалась бы не просто тупо в одну строчку,
а дробилась бы на несколько,т.е. продолжалась бы строка каждйый раз с новой строки,под предыдущей частью строки,которая
вверху,т.е. столбиком. Ведь реально такие текстовые файлы не кому не нужны,да и это не красиво.
Я пробовал использовать символы экранирования(или,наверно,управляющие символы,как они ещё,видимо,называются), но
в ассемблере,как в языке"C++",они совершенно не работают, в чём вы легко убедитесь,если запустите прграмму
"WriteToFile.exe",которую прилагаю,вместо нужного переноса происходит прсто запись в одну строчку вместе с
самим управляющим символом, вот так:

ќв® Їа®бв®\r\nпробное письмо

Это просто безобразие,и такие"записи" не нужны в принципе.Толку от них нет,
никуда не применимы.
И как же тогда сделать в ассемблере перенос на строку ниже,с помощью какой,может быть,функции,
это необходимо прежде всего уметь делать. Вот такой вот досадный затык.
P.S.: И использование других символов,как например"\0","\n","n\r\" и т.п. приводят к такому же "результату".
Очень буду признателен,если исправите мою эту программу в правильную сторону.
И очень странно,почему в литературе этот вопрос забывают осветить?
Заранее спасибо,с уважением,Сергей.

P.S.:
А вот это прилагаемый листинг моей пробной программы:




;###########################################################################
;В этой проге предприеята просто попытка записать стролки с переносом на следующую строку в созданный
;талько что текстовой файл.
;
;###########################################################################


.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
include \masm32\include\gdiplus.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\gdiplus.lib

WOW proto :DWORD,:DWORD,:DWORD,:DWORD



.data
; struct SYSTEM_INFO SystemInfo
SystemInfo SYSTEM_INFO <>
; struct OSVERSIONINFOA
ovinfo OSVERSIONINFOA <> ;"ovinfo" - это экземпляр стр-ры OSVERSIONINFOA

;dword_1 dd 94h dup(0)
;gVer dd 0
;Txt db 'Наша системная информация',0
TextFile db 'D:\NewFile.txt',0 ;Тут будет располагаться наш создаваемый батник
hFile dd 0
NumberOfBytesWritten dd 0
;Err db 'Ошибка создания файла!',0
WrHdl dd 0
FileName db 'D:\SuperDial.exe',0
TextBatFile dd 2400 dup(?) ;Буфер для финального текста,который будет располагаться потом в батнике
BatText db 'Это просто\r\n',0
TwoBatText db 'пробное письмо',0
Buffer dd 0

.data?
hInstance dd ?
CommandLine dd ?

.code
start:
;Присыоим файлу,который будем удалять,нормальные атрибуты
;push FILE_ATTRIBUTE_NORMAL
;push offset VirusFileName
;call SetFileAttributes
;А тут мы создадим наш батник,пока он будет пустой
push NULL ;указатель на шаблон,который будет использ-ся для создания ф-ла
push FILE_ATTRIBUTE_NORMAL ;or FILE_FLAG_SEQUENTIAL_SCAN ;Атрибуты создаваемого ф-ла- нормальное состоянте ф-ла
;эта конст-та"FILE_ATTRIBUTE_NORMAL" имеет числовое выражение 80h.
push CREATE_ALWAYS ;способ открытия ф-ла - всегда создавать,если ф-л уже существует,
;то данные будут перезаписаны.Пар-р "CREATE_ALWAYS"имннт числовое выражение "2h"
push NULL ;Атрибуты безапосности - не используются.
push NULL ;FILE_SHARE_READ or FILE_SHARE_WRITE ;Режим доступа к открытому ф-лу другим прогам -
;- другим приложениям разрешены чтение и запись. В числовом выр-нии это "3"
push GENERIC_WRITE ;Режим доступа - открыт для записи.
; В числовом выр-нии эти константы имеют значение.
push offset TextFile ;путь, где должен располагаться создаваемый ф-л
call CreateFile ;После вып-ния этого вызова в "EAX" загрузилось число 00000034h,
;а так же изменились значения в рег-рах "ECX","EDX","ESP"
mov hFile,eax
;Теперь преобразуем нашу строку в OEM-определённые установленные символы
push offset BatText
push offset BatText
call CharToOem ;The CharToOem function translates a string into the OEM-defined character set.
;(OEM stands for original equipment manufacturer.)
push offset BatText
call lstrlen
push NULL ;Тут строка, которая используется только в режиме наложения
push offset NumberOfBytesWritten ;!!!Адрес(смещение) в памяти,по которому сохраняется количество
;записываемых в файл байтов.Тут должно быть именно смещение!
push eax ;Количество байтов данных для записи
push offset BatText ;Данные,которые надо записать
push hFile ;Указатель на открытый файл
call WriteFile ;После вып-ния этой команды в "EAX"загрузилоось число"0012FF9A",да и

push offset TwoBatText
call lstrlen
push NULL ;Тут строка, которая используется только в режиме наложения
push offset NumberOfBytesWritten ;!!!Адрес(смещение) в памяти,по которому сохраняется количество
;записываемых в файл байтов.Тут должно быть именно смещение!
push eax ;Количество байтов данных для записи
push offset TwoBatText ;Данные,которые надо записать
push hFile ;Указатель на открытый файл
call WriteFile ;После вып-ния этой команды в "EAX"загрузилоось число"0012FF9A",да и




push hFile
call CloseHandle

push NULL
call ExitProcess


end start



Ответ.

Добрый день Сергей!

Я посмотрел программу и постарался вникнуть в вашу проблему. Теперь я все понял и постараюсь подробно ответить.
Поднятый вами вопрос очень интересен. Постараюсь ответить на него подробно.
Любой файл состоит из последовательности байтов. Значение байтов, как известно, оказывается в промежутке от 0 до 255. В принципе все они совершенно равноправны, если только мы не напишем программу, или функцию, которая будет различать их по значению. Таким образом, если пользуетесь функцией, которая не различает байты, то надо это учитывать. Соответственно это касается функций, которая различает байты по значению и, соответственно, в зависимости от этих значений по разному с ними работает. Кстати вы пользуетесь API-функциями, для которых все байты одинаковы. Функции для работы с текстовыми файлами есть в библиотеке C.
Еще с незапамятных времен байты, значения которых от 0 до 31 стали считать управляющими байтами, т.е. они использовались в служебных целях. Среди них следует выделить два магических байта 13 и 10. Эти байты используются для отметки конца строки текста. Специальные функции С для работы с текстовыми файлами их понимают. Если же, как в вашей программе, использовать универсальные функции для работы с файлами, то для отметки конца строки вам придется самому в конце строки подставлять эти байты. Надо отметить, что в разных операционных системах используются разное сочетание этих байтов для обозначения конца строки:
MS DOS - Windows - последовательность 13,10.
Linux – 10.
Mac OS – 13.
И так, например, вам следует написать строку так
BatText db 'Это просто ',13,10,0
Ну и так далее.
Специальные обозначения типа \n и т.п. используются в языке C, в функциях, которые их понимают, например семействе printf. Подобные же обозначения используются во многих скриптовых языках. В ассемблере же они бесполезны.

С наилучшими пожеланиями, Пирогов В.Ю.

PS
Пишите, если будут вопросы.

Пирогов В.Ю. (http://asm.shadrinsk.net)

64-битовое программирование в Linux на ассемблере
pirogov_vju


Для вызова системных функций в Unix-системах на ассемблере можно использовать системный вызов (шлюз) 0x80. Однако, во-первых, нотация вызова может меняться от версии к версии, во-вторых, от одной Unix-системы, к другой. Изначально, в качестве прослойки, наподобие  функций API в Unix-системах использовались функции из стандартных библиотек языка C.

В 64-битных Unix-системах также произошел переход к соглашению о передаче параметров по средством регистров. Для передачи параметров, представляющих целые числа (в том числе и ссылки)  используются последовательно шесть регистров: rdi, rsi, rdx, rcx, r8, r9. Если количество параметров превышает шесть, то оставшиеся параметры передаются через стек. При этом, как и раньше следует помнить о выравнивании стека по границе кратной 16 байтов. 
В качестве примера того, как параметры передаются системным функциям, приведем пример, в котором используются две системные функции gets, exit,  printf. Для того, чтобы рассмотреть возможность использования стека, нами был взят случай с большим количеством параметров (см. вызов функции printf).

;вызов системных функций на ассемблере Unix64
format ELF64 executable 3
entry start
include 'import64.inc'
interpreter '/lib64/ld-linux-x86-64.so.2'
needed 'libc.so.6'
import exit, gets, printf
segment readable executable
start:
;в начале организуем стек
push rbp
mov  rbp,rsp
;выделить стек, чтобы в дальнейшем передать через него параметры
sub  rsp,32
;получить строку
mov rdx,1
mov  rsi,100
mov  rdi, buf
call [gets]
;вывод данных с помощью функции printf
;в начале выделим стек для стековых параметров (их будет три)
;параметры
mov  rdi,frmt
mov  rsi,buf
mov  rdx,32
mov  rcx,64
mov  r8,128
mov  r9,256
mov rax,512
mov  [rsp],rax
mov rax,1024
mov [rsp+8],rax
mov rax,2048
mov [rsp+16],rax
call [printf]
;восстановить стек
mov rsp,rbp
pop rbp
;выход из программы
mov  rdi,0
call [exit]
segment readable writeable
buf   db 100 dup(0)
frmt  db "%s %d %d %d %d %d %d %d ",10,0

Остановимся на некоторых важных моментах программы.
1. Для доступа к стеку, мы используем не команды push, а прямой доступ к области стека.
2. Для трех параметров (этим параметрам не хватило регистров и они будут передаваться через стек) мы выделяем 32 байта (команда sub rsp,32), так чтобы стек был выровнен по адресу, кратному 16 (Команды push rbp/sub rsp,32 плюс вызов процедуры (еще 8 байтов) в результате дают смещение в стеке равное 48 байтов, что кратно 16).
Для передачи параметров имеющих вещественный тип (64-х битовый) также как и в случае операционной системы Windows используются регистры группы xmm. Для возвращения данных из функции используются  регистры rax и xmm0.
 


Как и раньше для трансляции достаточно написать fasm prog.asm и получаем исполняемый модуль.

Продолжение следует...

Пирогов В.Ю. (http://asm.shadrinsk.net

64-битовое программирование в Windows на ассемблере
pirogov_vju
Первой вопрос, который, я хочу рассмотреть и  который, мне кажется, сейчас очень важным, это 64-битовое программирование.  Естественно речь пойдет об ассемблере. В качестве такового возьмем  fasm. Во-первых, поддерживает 64-битовое программирование, во-вторых, этот ассемблер является кроссплатформенным. Для того, чтобы начать писать 64-битовые программы достаточно 1. Узнать структуру программы; 2. Использовать правильное соглашение вызова системных и вообще функций.
Программирование в Windows.
В 64-битовых Windows принята следующая конвенция вызова функций (в том числе вызов функций API).
1. Первые четыре параметра передаются в функцию через регистры: rcx, rdx, r8, r9. Остальные параметры (если они есть) передаются через стек.
2. Перед вызовом функции резервируется область в стеке, на случай, если вызываемая функция "захочет" временно сохранить параметры в стеке. Таким образом, параметры, которые передаются через стек, помещаются туда после резервируемой области.
3. При передаче параметров, размер которых меньше 64 бит, передаются как 64-битовые параметры. При этом следует обнулить старшие биты. Параметры, большие 64-бит передаются по ссылке.
4. Данные возвращаются через регистр rax. Если возвращаемое значение имеет размер больший 64 бит, то данное передается через область памяти, адрес которой передается в первом параметре. Для возвращения может также использоваться регистр xmm0.
5. Все регистры при вызове функций сохраняются за исключением rax, rcx, rdx, r8, r9, r10, r11, сохранность которых не гарантируется.
6. Граница стека должна быть выровнена по адресу кратному 16.
Рассмотрим в общих чертах схему вызова функции API с пятью параметрами.

...
sub rsp,40 ; резервируем стек
mov qword ptr [rsp+32],par5
mov r9,par4
mov r8,par3,
mov rdx,par2
mov rcx,par1
call f_api64
...
add rsp,40 ;восстанавливаем стек
...


Обратите внимание, что в нашем случае стек с учетом адреса возврата при вызове функции оказывается выровненным на величину кратную 16 (48 байт). С учетом такого выравнивания вызов функции, содержащей только 4 параметра будет выглядеть так

sub rsp,40 ; резервируем стек
mov r9,par4
mov r8,par3,
mov rdx,par2
mov rcx,par1
call f_api64
...
add rsp,40 ;восстанавливаем стек


Рассмотрим следующую программу

format PE64 GUI
entry start
section '.text' code readable executable
start:
sub rsp,8*5
mov r9,0
lea r8,[_caption]
lea rdx,[_message]
mov rcx,0
call [MessageBoxA]
add rsp,40
sub rsp,16
mov ecx,eax
call [ExitProcess]
section '.data' data readable writeable
_caption db 'Win64 assembly program',0
_message db 'Hello World!',0
section '.idata' import data readable writeable
dd 0,0,0,RVA kernel_name,RVA kernel_table
dd 0,0,0,RVA user_name,RVA user_table
dd 0,0,0,0,0
kernel_table:
ExitProcess dq RVA _ExitProcess
dq 0
user_table:
MessageBoxA dq RVA _MessageBoxA
dq 0
kernel_name db 'KERNEL32.DLL',0
user_name db 'USER32.DLL',0
_ExitProcess dw 0
db 'ExitProcess',0
_MessageBoxA dw 0
db 'MessageBoxA',0


Если имя программы prog.asm, то компилируется она просто командой
fasm prog.asm


Как видите, в Windows все просто.

Пирогов В.Ю, (
http://asm.shadrinsk.net)


Linux 64, продолжение следует...

Объяснение
pirogov_vju
Вот уже двадцать пять с лишним лет я программирую, преподаю, пишу книги. В 2003-м году появился в сети мой сайт Ассемблер и не только (http://asm.shadrinsk.net). Сайт был посвящен в основном программированию на языке ассемблера.
В прошлом году я полностью переделал сайт, однако чувство неудовлетворенности не оставляет меня. Дело в том, что на сайте отсутствует форум и блог. Времени у меня слишком мало, чтобы разрабатывать самому, а чужими продуктами пользоваться не хочется. С другой стороны есть настоятельная необходимость некоторого общения с читателями. На сайт присылаются вопросы, ответы на которые могли бы быть интересны и другим любителям программирования. Я подумал, что ЖЖ это идеальная площадка для подобного общения. Ну, по крайнй мере, пока у меня на сайте не заработает свой блог.
Мой блог посвящен только темам, так или иначе связанным с программированием и информационными технологиями. В нем не будут обсуждаться другие вопросы нашей жизни, которые меня, естественно интересуют (философия, искусство и т.д.).

Конструктор
pirogov_vju

Никакой информации, просто конструктор.


?

Log in