Ассемблер 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-битная версия программы:
Результат работы 32-битной версии программы


Ответы (2 шт):

Автор решения: user7860670

Процедуры, включая точку входа, следует обозначать блоками proc/ endp

StartOfProgram proc
...
StartOfProgram endp

end

Имя с глобальным блоком, как и указание точки входа после end - это все вроде как атавизмы. Так что всегда используйте proc/ endp и /entry:.

А про необходимость _ в имени функции вроде как уже выяснили в прошлом вопросе.

→ Ссылка
Автор решения: Developer

Проблема решена. Есть два варианта её решения: один как сказал(а) @user7860670 - через директивы "proc" и "endp", другой, о котором я узнал, - через директиву "public"


Я предпочитаю второй вариант решения - через директиву "public" - ,так как считаю что он более близок к низкоуровневому программированию. В этом втором варианте мне нужно было сделать свою метку публичной в моей программе с помощью директивы "public", а затем написать имя метки, для того чтобы она стала доступной для внешних программ. Ассемблер MASM, видимо, выдавал ошибку из-за того что не видел её доступной извне и поэтому не считал правильным назначать её точкой входа, хотя сам мог догадаться что если я указываю её точкой входа, значит она доступна для перехода на неё извне. Видимо, разработчики MASM-а не сделали это.
Вот пример использования этой директивы в моей программе:
public StartOfProgram
Причём, я заметил что эту директиву я могу ставить в любом месте своей программы

А вариант решения через директивы "proc" и "endp" делается установкой имени метки перед этими директивами (как написано в документации от Microsoft) и установкой начала ("proc") и конца ("endp") этой директивной процедуры.
Пример, как и показал(а) @user7860670:

StartOfProgram proc     ; - Начало директивной процедуры

; ... здесь может быть сам код внутри этой директивной процедуры

StartOfProgram endp     ; - Конец директивной процедуры

Видимо в каких-то случаях ассемблер MASM понимает что метка точки входа публична, а в каких-то не понимает, хоть и должен понимать что метка точки входа публична "по определению", по смыслу, и логике

→ Ссылка