Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

Windows 환경에서의 Hello World x64 어셈블리 예제 (NASM 버전)

윈도우에서의 간단한 어셈블리 예제를 MASM 버전으로 알아봤는데요,

Windows 환경에서의 Hello World x64 어셈블리 예제 (MASM 버전)
; https://www.sysnet.pe.kr/2/0/13182

MASM + CRT 함수를 사용하는 경우 발생하는 컴파일 오류 정리
; https://www.sysnet.pe.kr/2/0/13183

ml64.exe와 link.exe x64 실행 환경 구성
; https://www.sysnet.pe.kr/2/0/13184

당연히 NASM(Netwide Assembler) 버전도 리눅스만 지원하는 것이 아니고 윈도우도 지원합니다. 게다가 Windows 11을 사용하고 있다면, (이제는 기본 설치된) winget을 이용해 이렇게 쉽게 설치할 수 있습니다.

c:\temp> winget install --id=NASM.NASM
Found NASM [NASM.NASM] Version 2.15.05
This application is licensed to you by its owner.
Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.
Downloading https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/win64/nasm-2.15.05-installer-x64.exe
  ██████████████████████████████   992 KB /  992 KB
Successfully verified installer hash
Starting package install...
Successfully installed

nasm 설치는 다음의 디렉터리에 구성되는데,

%USERPROFILE%\AppData\Local\bin\NASM

winget 설치 단계에서 함께 생성되는 바탕화면의 (nasmpath.bat을 가리키는) "nasm" 단축 아이콘을 통해 명령행 환경으로 진입할 수 있습니다. 혹은, 위의 경로를 윈도우의 환경 변수 설정에 PATH로 추가하던지, 아니면 위의 경로에 있는 "nasmpath.bat" 파일을 직접 실행해 명령행 창을 띄우면 됩니다.

하지만 그렇게 해서 nasm.exe를 실행해 어셈블리 언어를 컴파일해도, 이후의 링킹 작업은 할 수 없습니다. 이를 위해 link.exe가 필요한데요, 어쩔 수 없이 이것 때문에라도 "Visual Studio 2022 (Community)" 또는 "Build Tools for Visual Studio 2022"를 설치해야 합니다. (이때 구성 요소로 "C++을 사용한 데스크톱 개발(Desktop development with C++)"을 선택해야 합니다.)

이렇게 nasm과 link가 준비되었으면 지난 글에 설명한 PATH와 LIB 환경 변수를 설정하는 것으로 빌드 환경 구성이 마무리됩니다.

// Visual Studio 2022 Enterprise 버전인 경우
// 14.34.31933 버전은 "... Command Prompt for VS 2022" 명령행 창에서 VCToolsVersion 환경 변수와 연결돼 있습니다.
// 10.0.22000.0 버전은 "... Command Prompt for VS 2022" 명령행 창에서 UCRTVersion 환경 변수와 연결돼 있습니다.

c:\temp> SET PATH=%PATH%;C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.34.31933\bin\Hostx64\x64

c:\temp> SET LIB=C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.34.31933\lib\x64;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\ucrt\x64;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22000.0\um\x64




자, 이렇게 해서 환경 구성이 되었으면 이제 지난번 MASM으로 했던 실습을 그대로 nasm 문법으로 변환해 컴파일할 수 있습니다. ^^

우선, 콘솔 출력을 하는 hello world 예제를 다음과 같이 구성할 수 있습니다.

; nhello.asm (nasm)

     section .data
message: db 'Hello, World!', 0
message_length equ $-message

    section .text
    global main
    extern  GetStdHandle
    extern  WriteFile
    extern  ExitProcess

main:
    sub  rsp, 28h  

    ; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
    mov     rcx, -11
    call    GetStdHandle

    ; WriteFile( hstdOut, message, length(message), &bytes, 0);
    mov     rcx, rax
    mov     rdx, message
    mov     r8, message_length
    mov     r9, 0
    push    0
    call    WriteFile

    ; ExitProcess(0)
    mov     rcx, rax
    call    ExitProcess

보는 바와 같이 문법 자체만 NASM을 따를 뿐, 내부의 ABI는 윈도우의 x64 체계를 따르기 때문에 Macro Assembler와 비교해 어셈블리 코드 자체는 거의 동일합니다.

물론, 빌드 및 실행까지 잘됩니다.

c:\temp> nasm -f win64 nhello.asm

// 또는 이렇게 Link
// link /subsystem:console /nodefaultlib /entry:main nhello.obj kernel32.lib

c:\temp> link /subsystem:console /entry:main nhello.obj kernel32.lib
Microsoft (R) Incremental Linker Version 14.12.25835.0
Copyright (C) Microsoft Corporation.  All rights reserved.


c:\temp> nhello
Hello, World!




그다음 MessageBox 예제는 별다르게 신기할 것이 없고,

; nhello_msgbox.asm

    section .data
msgtitle db 'x64 App', 0
message db 'Hello World!', 0

    section .text
    global main
    extern ExitProcess
    extern MessageBoxA

main:
    sub  rsp, 28h  

    mov     rcx, 0
    mov     rdx, message
    mov     r8, msgtitle
    mov     r9, 0
    call    MessageBoxA

    mov     rcx, rax
    call    ExitProcess

c:\temp> nasm -f win64 nhello_msgbox.asm

// 또는 이렇게 Link
// c:\temp> link /subsystem:console /nodefaultlib /entry:main nhello_msgbox.obj kernel32.lib user32.lib

c:\temp> link /subsystem:console /entry:main nhello_msgbox.obj kernel32.lib user32.lib
Microsoft (R) Incremental Linker Version 14.12.25835.0
Copyright (C) Microsoft Corporation.  All rights reserved.

c:\temp> nhello_msgbox.exe
...[MessageBox 창]...

CRT 연결하는 것도,

; hello_cpp.asm

    section .data
message db 'Hello World!', 0

    section .text
    global main
    extern ExitProcess
    extern printf

main:
    sub  rsp, 28h  

    mov     rcx, message
    call    printf

    mov     rcx, rax
    call    ExitProcess

간단하게 해결됩니다.

c:\temp> nasm -f win64 nhello_cpp.asm

// 또는 이렇게 Link
// link /subsystem:console /nodefaultlib nhello_cpp.obj vcruntime.lib kernel32.lib ucrt.lib libcmt.lib legacy_stdio_definitions.lib legacy_stdio_wide_specifiers.lib /entry:mainCRTStartup

c:\temp> link /subsystem:console nhello_cpp.obj kernel32.lib libcmt.lib /entry:mainCRTStartup
Microsoft (R) Incremental Linker Version 14.34.31933.0
Copyright (C) Microsoft Corporation.  All rights reserved.


c:\temp> nhello_cpp
Hello World!




다음과 같은 오류가 발생한다면?

c:\temp> link /subsystem:console /nodefaultlib /entry:main nhello.obj kernel32.lib
Microsoft (R) Incremental Linker Version 14.12.25835.0
Copyright (C) Microsoft Corporation.  All rights reserved.

nhello.obj : error LNK2017: 'ADDR32' relocation to '.data' invalid without /LARGEADDRESSAWARE:NO
LINK : fatal error LNK1165: link failed because of fixup errors

메시지에서 알려주는 것처럼 link에 /LARGEADDRESSAWARE:NO 옵션을 주면 오류 없이 깨끗하게 빌드 및 실행이 되긴 합니다.

c:\temp> link /subsystem:console /nodefaultlib /entry:main nhello.obj kernel32.lib /LARGEADDRESSAWARE:NO
Microsoft (R) Incremental Linker Version 14.12.25835.0
Copyright (C) Microsoft Corporation.  All rights reserved.

c:\temp> nhello.exe
Hello, World!

하지만 이렇게 되면 nhello.exe 프로세스 내의 모든 모듈들이 2GB 내의 주소 공간에 배치가 됩니다. 사실 이게 좀 이해가 안 되는데요, 위의 오류가 발생하는 원인은 MASM으로 만들었던 이전 예제 코드(hello.asm)에서는 아무 문제 없이 컴파일이 되었던 아래의 코드 때문입니다.

     section .data
message: db 'Hello, World!', 0

    section .text
        ; ...[생략]...
    lea     rdx, [message]

아마도 nasm이 멀티 플랫폼을 지원해서 그런 것인지... lea 명령어의 두 번째 operand가 32비트 값으로 취급하는 어떤 제약이 있는 것이 아닌가... 생각됩니다. (혹시 위의 코드가 왜 nasm에서 오류인지 아시는 분은 덧글 부탁드리겠습니다. ^^ )

어쨌든, lea가 아닌 (이번 글의 예제에서는 수정해서 적용한) "mov rdx, message" 명령어를 사용해 대체하면 아무런 오류 없이 빌드가 잘됩니다. 특이하게도 동일한 유형의 lea 코드를 리눅스/WSL에서 실습하면 컴파일/링크 시에 아무런 오류가 없습니다.

// 윈도우와 달리 리눅스/WSL에서 빌드하면,
// $ nasm -f elf64 -o app.o app.asm
// $ ld app.o -o app
// lea의 두 번째 operand에 대한 오류가 없음

section .data
    message: db "Hello World!", 0x0a
    message_length equ $-message

section .text
global _start

_start:
    mov rax, 1 ; sys_write
    mov rdi, 1 ; stdout
    ; mov rsi, message ; buf
    lea rsi, [message] // mov rsi, message를 lea 명령어로 바꿈
    mov rdx, message_length ; count
    syscall

    mov rax, 60 ; sys_exit
    mov rdi, 0 ; error code 0
    syscall




다음과 같은 오류가 발생한다면?

error LNK2001: unresolved external symbol __volatile_metadata

bufferoverflowu.lib를 함께 링크하면 됩니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 12/6/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13503정성태12/27/20232191Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20232298닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
13501정성태12/25/20232098개발 환경 구성: 700. WSL + uwsgi - IPv6로 바인딩하는 방법
13500정성태12/24/20232180디버깅 기술: 194. Windbg - x64 가상 주소를 물리 주소로 변환
13498정성태12/23/20232857닷넷: 2186. 한국투자증권 KIS Developers OpenAPI의 C# 래퍼 버전 - eFriendOpenAPI NuGet 패키지
13497정성태12/22/20232296오류 유형: 885. Visual Studiio - error : Could not connect to the remote system. Please verify your connection settings, and that your machine is on the network and reachable.
13496정성태12/21/20232311Linux: 66. 리눅스 - 실행 중인 프로세스 내부의 환경변수 설정을 구하는 방법 (gdb)
13495정성태12/20/20232322Linux: 65. clang++로 공유 라이브러리의 -static 옵션 빌드가 가능할까요?
13494정성태12/20/20232502Linux: 64. Linux 응용 프로그램의 (C++) so 의존성 줄이기(ReleaseMinDependency) - 두 번째 이야기
13493정성태12/19/20232564닷넷: 2185. C# - object를 QueryString으로 직렬화하는 방법
13492정성태12/19/20232266개발 환경 구성: 699. WSL에 nopCommerce 예제 구성
13491정성태12/19/20232233Linux: 63. 리눅스 - 다중 그룹 또는 사용자를 리소스에 권한 부여
13490정성태12/19/20232350개발 환경 구성: 698. Golang - GLIBC 의존을 없애는 정적 빌드 방법
13489정성태12/19/20232138개발 환경 구성: 697. GoLand에서 ldflags 지정 방법
13488정성태12/18/20232070오류 유형: 884. HTTP 500.0 - 명령행에서 실행한 ASP.NET Core 응용 프로그램을 실행하는 방법
13487정성태12/16/20232384개발 환경 구성: 696. C# - 리눅스용 AOT 빌드를 docker에서 수행 [1]
13486정성태12/15/20232199개발 환경 구성: 695. Nuget config 파일에 값 설정/삭제 방법
13485정성태12/15/20232088오류 유형: 883. dotnet build/restore - error : Root element is missing
13484정성태12/14/20232162개발 환경 구성: 694. Windows 디렉터리 경로를 WSL의 /mnt 포맷으로 구하는 방법
13483정성태12/14/20232302닷넷: 2184. C# - 하나의 resource 파일을 여러 프로그램에서 (AOT 시에도) 사용하는 방법파일 다운로드1
13482정성태12/13/20232878닷넷: 2183. C# - eFriend Expert OCX 예제를 .NET Core/5+ Console App에서 사용하는 방법 [2]파일 다운로드1
13481정성태12/13/20232272개발 환경 구성: 693. msbuild - .NET Core/5+ 프로젝트에서 resgen을 이용한 리소스 파일 생성 방법파일 다운로드1
13480정성태12/12/20232615개발 환경 구성: 692. Windows WSL 2 + Chrome 웹 브라우저 설치
13479정성태12/11/20232311개발 환경 구성: 691. WSL 2 (Ubuntu) + nginx 환경 설정
13477정성태12/8/20232485닷넷: 2182. C# - .NET 7부터 추가된 Int128, UInt128 [1]파일 다운로드1
13476정성태12/8/20232212닷넷: 2181. C# - .NET 8 JsonStringEnumConverter의 AOT를 위한 개선파일 다운로드1
1  2  3  4  [5]  6  7  8  9  10  11  12  13  14  15  ...