Ассемблер MASM (64-битный) не распознаёт точку входа и выдаёт ошибку
Изучаю ассемблер для семейства архитектур процессоров x86 (32-битных и 64-битных) на Windows. Не сказать что совсем прям новичок, но наверное не все знаю, по крайней мере про синтаксис ассемблера MASM, как видимо. Использую ассемблер MASM (для 64-битных программ) находящийся в папках, принадлежащих Visual Studio:
"..\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\ml64.exe"
Установлен Visual Studio 2019 года, из его папки и использую ассемблер MASM. У меня самого́ Windows 7
Я сделал мою программу для 32-битной системы, она нормально ассемблировалась MASM-ом для 32 битных программ и работала. Затем перевел её код под 64-битную архитектуру (а изменений там в коде необходимо мало). Но, при ассемблировании её MASM-ом для 64-битных программ, MASM выдавал сообщение об ошибке, что якобы есть какой-то нерешённый символ "StartOfProgram". Вот что в консоли:
C:\Assembler>cd "C:\Assembler"
C:\Assembler>"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\ml64.exe" "C:\Assembler\Main.asm" /link /subsystem:windows /entry:StartOfProgram
Microsoft (R) Macro Assembler (x64) Version 14.29.30138.0
Copyright (C) Microsoft Corporation. All rights reserved.
Assembling: C:\Assembler\Main.asm
Microsoft (R) Incremental Linker Version 14.29.30138.0
Copyright (C) Microsoft Corporation. All rights reserved.
/OUT:Main.exe
Main.obj
/subsystem:windows
/entry:StartOfProgram
LINK : error LNK2001: неразрешенный внешний символ StartOfProgram.
Main.exe : fatal error LNK1120: неразрешенных внешних элементов: 1
Где-то 2 недели или месяц провозился в поисках решения этой ошибки, но не нашёл
Вообще, раньше он выдавал сообщение об ошибке, что якобы есть какой-то неразрешённый символ "WinMainCRTStartup", но недавно вроде понял, что это он такую точку входа сделал из-за того что я явно не указал точку входа в консоли (через команду "/entry:", которая есть в консоли сверху), но проблема про "неразрешенный внешний символ" осталась, даже несмотря на то что я установил точку входа там где мне надо (то есть, на "StartOfProgram")
Вот код моей 64-битной версии программы которая просто должна выводить "Hello world" во всплывающем окне:
option casemap:none ; Насколько я понял, функции из Windows API без чувствительности к регистру не работают
; **** Импорт того что нужно ****
includelib "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64\kernel32.lib" ; Загружаю главную статическую библиотеку для использования основных Windows API-ских функций
extern LoadLibraryA:near ; Загружаю из статических библиотек используемые в этой программе функции
extern GetProcAddress:near
extern FreeLibrary:near
extern ExitProcess:near
; **** Объявление сегмента памяти ****
.data
text db 'Hello world', 0 ; Текст в "Text Box"-овском окне
header db 'Title of hello world', 0 ; Заголовок "Text Box"-овского окна
nameOfDLL db 'user32.dll', 0
nameOfProcedureOfDLL db 'MessageBoxA', 0
handlerToModule dd 0 ; Возвращаемый Windows-овскими функциями handler везде имеет размер 32 бита, и в 32-битных программах, и в 64-битных
addressOfProcedureOfDLL dq 0 ; В 64-битной операционной системе адреса 64-битные, поэтому размер области памяти на которую указывает эта метка - quad word (dq) (четверное слово) (то есть 64 бита)
.code
; **** Точка входа в программу ****
StartOfProgram: ; Ассемблер MASM почему-то рекомендует ставить знак "_" перед меткой точки входа в программу, если она 32-битная. Поэтому здесь, в 64-битной, наконец-то не ставлю
mov rcx, offset nameOfDLL
sub rsp, 40
call LoadLibraryA
add rsp, 40
mov qword ptr handlerToModule, rax
mov rcx, rax ; В регистре осталось возвращаемое значение после LoadLibraryA, а регистры быстрее чем оперативная память, значит использовать регистр
mov rdx, offset nameOfProcedureOfDLL
sub rsp, 40
call GetProcAddress
add rsp, 40
mov addressOfProcedureOfDLL, rax
mov rcx, 0
mov rdx, offset text
mov r8, offset header
mov r9, 0
sub rsp, 40
call addressOfProcedureOfDLL ; Поставил адрес, а не rax (в rax всё ещё остался этот адрес), потому что компьютер сразу перейдёт по адресу, вместо того чтобы сначала читать этот адрес с rax
add rsp, 40
mov rcx, offset handlerToModule
sub rsp, 40
call FreeLibrary
add rsp, 40
mov rcx, 0
sub rsp, 40
call ExitProcess
add rsp, 40
end
Вот код моей 32-битной версии этой программы (которая нормально ассемблировалась и работала):
.386 ; Указывается процессор с минимальным набором функций (так как новые процессоры Intel (в семействе архитектур "x86") совместимы (пока что) с инструкциями старых процессоров Intel того же семейства архитектур)
option casemap:none ; Насколько я понял, функции из Windows API без чувствительности к регистру не работают
; **** Импорт того что нужно ****
includelib "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86\kernel32.lib" ; Загружаю главную статическую библиотеку без которой (насколько я понял) не заработает ни одна Windows-программа (то есть неконсольная)
;includelib "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x86\User32.lib"
extern _LoadLibraryA@4:near ; Загружаю из статических библиотек используемые в этой программе функции
extern _GetProcAddress@8:near ; ^
extern _FreeLibrary@4:near ; ^
extern _ExitProcess@4:near ; ^
.model flat
; **** Объявление сегмента памяти ****
.data
text db 'Hello world', 0 ; Текст в "Text Box"-овском окне
header db 'Title of hello world', 0 ; Заголовок "Text Box"-овского окна
nameOfDLL db 'user32.dll', 0
nameOfProcedureOfDLL db 'MessageBoxA', 0
handlerToModule dd 0
addressOfProcedureOfDLL dd 0
.code
; **** Точка входа в программу ****
_StartOfProgram: ; Ассемблер MASM почему-то рекомендует ставить знак "_" перед меткой точки входа в программу, если она 32-битная
push offset nameOfDLL
call _LoadLibraryA@4 ; Динамически подключаю DLL чтобы потом взять из неё функцию
mov handlerToModule, eax
push offset nameOfProcedureOfDLL
push eax ; Функции для 32-битных программ из Windows API используют соглашение stdcall. stdcall - это соглашение о передаче параметров функции в стэк задом наперёд, поэтому eax последним. В eax пока-что содержится Windows-овский адрес DLL (Microsoft называют его "handler"-ом) (после недавнего вызова функции LoadLibraryA), поэтому лучше регистр использовать, процессор быстрее работает с регистрами
call _GetProcAddress@8
mov addressOfProcedureOfDLL, eax ; Сохраняю адрес процедуры, который взял из GetProcAdress
push 0
push offset header
push offset text
push 0
call addressOfProcedureOfDLL
push handlerToModule
call _FreeLibrary@4
push 0
call _ExitProcess@4
end _StartOfProgram
И вот какой результат выдавала 32-битная версия программы:

Ответы (2 шт):
Процедуры, включая точку входа, следует обозначать блоками proc/ endp
StartOfProgram proc
...
StartOfProgram endp
end
Имя с глобальным блоком, как и указание точки входа после end - это все вроде как атавизмы. Так что всегда используйте proc/ endp и /entry:.
А про необходимость _ в имени функции вроде как уже выяснили в прошлом вопросе.
Проблема решена. Есть два варианта её решения: один как сказал(а) @user7860670 - через директивы "proc" и "endp", другой, о котором я узнал, - через директиву "public"
Я предпочитаю второй вариант решения - через директиву "public" - ,так как считаю что он более близок к низкоуровневому программированию. В этом втором варианте мне нужно было сделать свою метку публичной в моей программе с помощью директивы "public", а затем написать имя метки, для того чтобы она стала доступной для внешних программ. Ассемблер MASM, видимо, выдавал ошибку из-за того что не видел её доступной извне и поэтому не считал правильным назначать её точкой входа, хотя сам мог догадаться что если я указываю её точкой входа, значит она доступна для перехода на неё извне. Видимо, разработчики MASM-а не сделали это.
Вот пример использования этой директивы в моей программе:
public StartOfProgram
Причём, я заметил что эту директиву я могу ставить в любом месте своей программы
А вариант решения через директивы "proc" и "endp" делается установкой имени метки перед этими директивами (как написано в документации от Microsoft) и установкой начала ("proc") и конца ("endp") этой директивной процедуры.
Пример, как и показал(а) @user7860670:
StartOfProgram proc ; - Начало директивной процедуры
; ... здесь может быть сам код внутри этой директивной процедуры
StartOfProgram endp ; - Конец директивной процедуры
Видимо в каких-то случаях ассемблер MASM понимает что метка точки входа публична, а в каких-то не понимает, хоть и должен понимать что метка точки входа публична "по определению", по смыслу, и логике