Windows에서 어셈블러로 Hello World를 작성하는 방법은 무엇입니까?


94

Windows에서 어셈블리에 기본적인 것을 작성하고 싶었는데 NASM을 사용하고 있지만 아무것도 작동하지 않습니다.

Windows에서 C 함수의 도움없이 Hello World를 작성하고 컴파일하는 방법은 무엇입니까?


3
Steve Gibson의 Small Is Beautiful Windows 어셈블리 스타터 키트 도 확인하십시오 .
Jeremy

c- 라이브러리를 사용하지 않는 것은 다소 이상한 제약입니다. MS-Windows 운영 체제 내에서 일부 라이브러리를 호출해야합니다. 아마도 kernel32.dll. Microsoft가 이것을 c 또는 Pascal로 작성했는지 여부는 관련이 없습니다. OS에서 제공하는 함수 만 호출 할 수 있다는 의미입니까? Unix 유형 시스템에서 시스템 호출이라고하는 것은 무엇입니까?
Albert van der Horst

C 라이브러리를 사용하면 GCC 또는 MSVC와 함께 제공되는 것과 같은 C 런타임 라이브러리를 사용하지 않고 의미한다고 가정합니다. 물론 kernel32.dll과 같은 표준 Windows DLL을 사용해야합니다.
Rudy Velthuis

2
kernel32.dll과 gcc 런타임 라이브러리의 차이는 형식 (둘 다 dll)이 아니며 언어 (둘 다 c 일 수 있지만 숨겨져 있음)가 아닙니다. 차이점은 OS 제공 여부입니다.
Albert van der Horst

나는 또한 이것을 찾고 있었는데 lol은 포함없이 fasm으로 아무것도 찾을 수 없었다
bluejayke

답변:


39

NASM 예 .

libc stdio 호출 printf, 구현int main(){ return printf(message); }

; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits.  It needs to be linked with a C library.
; ----------------------------------------------------------------------------

    global  _main
    extern  _printf

    section .text
_main:
    push    message
    call    _printf
    add     esp, 4
    ret
message:
    db  'Hello, World', 10, 0

그런 다음 실행

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

C 라이브러리를 사용하지 않는 Nasm의 Hello World에 대한 The Clueless Newbies Guide 도 있습니다. 그러면 코드는 다음과 같습니다.

MS-DOS 시스템 호출이있는 16 비트 코드 : DOS 에뮬레이터 또는 NTVDM을 지원하는 32 비트 Windows에서 작동합니다 . x86-64 커널은 vm86 모드를 사용할 수 없기 때문에 64 비트 Windows에서 "직접"(투명하게) 실행할 수 없습니다.

org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'

.com이를 실행 파일 로 빌드 cs:100h하여 모든 세그먼트 레지스터가 서로 동일한 상태로 로드되도록합니다 (작은 메모리 모델).

행운을 빕니다.


28
문제는 명시 적으로 "C 라이브러리를 사용하지 않고"언급
메흐 다드 Afshari에게

25
잘못된. C 라이브러리 자체는 분명히 가능하므로 가능합니다. 사실 약간 더 어렵습니다. 올바른 5 개의 매개 변수를 사용하여 WriteConsole ()을 호출하기 만하면됩니다.
MSalters

12
두 번째 예제는 C 라이브러리 함수를 호출하지 않지만 Windows 프로그램도 아닙니다. 이를 실행하기 위해 가상 DOS 머신이 실행됩니다.
Rômulo Ceccon 09.

7
@Alex Hart, 그의 두 번째 예는 Windows가 아닌 DOS 용입니다. DOS에서 작은 모드 (.COM 파일, 64Kb 총 코드 + 데이터 + 스택 미만)의 프로그램은 세그먼트의 처음 256 바이트가 PSP (명령 줄 인수 등)에 의해 사용되기 때문에 0x100h에서 시작합니다. 이 링크를 참조하십시오 : en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov

7
이것은 요청 된 것이 아닙니다. 첫 번째 예제는 C 라이브러리를 사용하고 두 번째 예제는 Windows가 아닌 MS-DOS입니다.
Paulo Pinto

131

이 예제는 C 표준 라이브러리에서 링크하지 않고 Windows API로 직접 이동하는 방법을 보여줍니다.

    global _main
    extern  _GetStdHandle@4
    extern  _WriteFile@20
    extern  _ExitProcess@4

    section .text
_main:
    ; DWORD  bytes;    
    mov     ebp, esp
    sub     esp, 4

    ; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
    push    -11
    call    _GetStdHandle@4
    mov     ebx, eax    

    ; WriteFile( hstdOut, message, length(message), &bytes, 0);
    push    0
    lea     eax, [ebp-4]
    push    eax
    push    (message_end - message)
    push    message
    push    ebx
    call    _WriteFile@20

    ; ExitProcess(0)
    push    0
    call    _ExitProcess@4

    ; never here
    hlt
message:
    db      'Hello, World', 10
message_end:

컴파일하려면 NASM 및 LINK.EXE (Visual Studio Standard Edition)가 필요합니다.

   nasm -fwin32 hello.asm
   링크 / subsystem : console / nodefaultlib / entry : main hello.obj 

21
이것을 링크하려면 kernel32.lib를 포함해야 할 것입니다. 링크 / 서브 시스템 : 콘솔 / NODEFAULTLIB / 항목 : 주요 hello.obj의 kernel32.lib
자크 벌린

5
obj와 MinGW의 ld.exe를 연결하는 방법은 무엇입니까?
DarrenVortex

4
@DarrenVortexgcc hello.obj
towry

4
이도에서 ALINK 같은 무료 링커를 사용하여 작동합니다 sourceforge.net/projects/alink 이나 GoLink godevtool.com/#linker ? 그 용도로만 Visual Studio를 설치하고 싶지 않습니까?
jj_

21

Windows API 호출을 사용하는 Win32 및 Win64 예제입니다. NASM이 아닌 MASM을위한 것이지만 살펴보십시오. 기사 에서 자세한 내용을 찾을 수 있습니다 .

이것은 stdout에 인쇄하는 대신 MessageBox를 사용합니다.

Win32 MASM

;---ASM Hello World Win32 MessageBox

.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib

.data
title db 'Win32', 0
msg db 'Hello World', 0

.code

Main:
push 0            ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg   ; LPCSTR lpText
push 0            ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax          ; uExitCode = MessageBox(...)
call ExitProcess

End Main

Win64 MASM

;---ASM Hello World Win64 MessageBox

extrn MessageBoxA: PROC
extrn ExitProcess: PROC

.data
title db 'Win64', 0
msg db 'Hello World!', 0

.code
main proc
  sub rsp, 28h  
  mov rcx, 0       ; hWnd = HWND_DESKTOP
  lea rdx, msg     ; LPCSTR lpText
  lea r8,  title   ; LPCSTR lpCaption
  mov r9d, 0       ; uType = MB_OK
  call MessageBoxA
  add rsp, 28h  
  mov ecx, eax     ; uExitCode = MessageBox(...)
  call ExitProcess
main endp

End

MASM을 사용하여 이들을 어셈블하고 링크하려면 32 비트 실행 파일에 다음을 사용하십시오.

ml.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main

또는 64 비트 실행 파일의 경우 :

ml64.exe [filename] /link /subsystem:windows 
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main

x64 Windows가 이전에 28h 바이트의 스택 공간을 예약해야하는 이유는 무엇 call입니까? 이는 호출 규칙에서 요구하는 32 바이트 (0x20)의 섀도우 공간 (홈 공간)입니다. 발신 협약 RSP 정렬 16 바이트 일 필요하기 때문에 또 다른 8 바이트 (16)에 의해 스택을 재 - 정렬 전에call . (우리 main의 호출자 (CRT 시작 코드에서)가 그렇게했습니다. 8 바이트 반환 주소는 RSP가 함수 진입시 16 바이트 경계에서 8 바이트 떨어져 있음을 의미합니다.)

함수는 섀도우 공간 을 사용하여 스택 인수 (있는 경우)가있는 위치 옆에 레지스터 인수를 덤프 할 수 있습니다. A system call는 앞서 언급 한 4 개의 레지스터 외에도 r10 및 r11을위한 공간을 예약하는 데 30 시간 (48 바이트)이 필요합니다. 그러나 DLL 호출은 syscall명령을 둘러싼 래퍼 일지라도 함수 호출 일뿐 입니다.

재미있는 사실 : Windows가 아닌 경우, 즉 x86-64 System V 호출 규칙 (예 : Linux)은 섀도우 공간을 전혀 사용하지 않으며 최대 6 개의 정수 / 포인터 레지스터 인수 XMM 레지스터에서 최대 8 개의 FP 인수를 사용합니다. .


MASM의 invoke지시문 (호출 규칙을 알고 있음)을 사용하면 하나의 ifdef를 사용하여 32 비트 또는 64 비트로 빌드 할 수있는 버전을 만들 수 있습니다.

ifdef rax
    extrn MessageBoxA: PROC
    extrn ExitProcess: PROC
else
    .386
    .model flat, stdcall
    include kernel32.inc
    includelib kernel32.lib
    include user32.inc
    includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text    db 'Hello World', 0
.code
main proc
    invoke MessageBoxA, 0, offset text, offset caption, 0
    invoke ExitProcess, eax
main endp
end

매크로 변형은 둘 다 동일하지만 어셈블리를 이런 식으로 배우지는 않습니다. 대신 C 스타일 asm을 배웁니다. invokefor stdcall또는 fastcallwhile cinvoke은 for cdecl또는 변수 인수 fastcall입니다. 어셈블러는 사용할 항목을 알고 있습니다.

출력을 분해하여 invoke확장 된 방식을 볼 수 있습니다 .


1
답변에 +1하세요. WOA (Windows on ARM) 용 어셈블리 코드도 추가해 주시겠습니까?
Annie

1
rsp에 0x20이 아닌 0x28 바이트가 필요한 이유는 무엇입니까? 호출 규칙에 대한 모든 참조는 32이어야하지만 실제로는 40이 필요한 것 같습니다.
douggard

32 비트 메시지 상자 코드에서 어떤 이유로 title레이블 이름으로 사용할 때 오류가 발생합니다. 그러나 같은 레이블 이름으로 다른 것을 사용 mytitle하면 모든 것이 잘 작동합니다.
user3405291

그것을 포함하는 방법?
bluejayke

14

플랫 어셈블러 에는 추가 링커가 필요하지 않습니다. 이것은 어셈블러 프로그래밍을 매우 쉽게 만듭니다. Linux에서도 사용할 수 있습니다.

이것은 hello.asmFasm 예제에서 가져온 것입니다.

include 'win32ax.inc'

.code

  start:
    invoke  MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
    invoke  ExitProcess,0

.end start

Fasm은 실행 파일을 생성합니다.

> fasm hello.asm
플랫 어셈블러 버전 1.70.03 (1048575 킬로바이트 메모리)
4 패스, 1536 바이트.

그리고 이것은 IDA 의 프로그램입니다 .

여기에 이미지 설명 입력

GetCommandLine, MessageBox및 이라는 세 가지 호출을 볼 수 있습니다 ExitProcess.


이것은 include 및 GUI를 사용합니다. include없이 CMD에 어떻게 수행합니까?
bluejayke

설명서를 읽어 보셨습니까? flatassembler.net/docs.php?article=manual#2.4.2
오전

dll없이 콘솔에 쓰는 섹션을 알려줄 수 있습니까?
bluejayke

12

NASM'compiler 및 Visual Studio의 링커를 사용하여 .exe를 얻으려면이 코드가 제대로 작동합니다.

global WinMain
extern ExitProcess  ; external functions in system libraries 
extern MessageBoxA

section .data 
title:  db 'Win64', 0
msg:    db 'Hello world!', 0

section .text
WinMain:
    sub rsp, 28h  
    mov rcx, 0       ; hWnd = HWND_DESKTOP
    lea rdx,[msg]    ; LPCSTR lpText
    lea r8,[title]   ; LPCSTR lpCaption
    mov r9d, 0       ; uType = MB_OK
    call MessageBoxA
    add rsp, 28h  

    mov  ecx,eax
    call ExitProcess

    hlt     ; never here

이 코드가 예를 들어 "test64.asm"에 저장되면 컴파일하려면 다음을 수행하십시오.

nasm -f win64 test64.asm

"test64.obj"를 생성합니다. 그런 다음 명령 프롬프트에서 연결합니다.

path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain  /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no

여기서 path_to_linkC : \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin 이거나 컴퓨터의 link.exe 프로그램이있는 위치에 있을 수 있으며 path_to_libsC : \ Program Files (x86) \ Windows Kits \ 8.1 \ 일 수 있습니다. Lib \ winv6.3 \ um \ x64 또는 라이브러리가 어디에 있든 (이 경우 kernel32.lib와 user32.lib는 모두 같은 위치에 있습니다. 그렇지 않으면 필요한 각 경로에 대해 하나의 옵션을 사용하십시오) 및 / largeaddressaware : no 옵션은 링커가 주소에 대한 불평을 피하는 데 필요합니다 (이 경우 user32.lib의 경우). 또한 여기서 수행되는 것처럼 Visual의 링커가 명령 프롬프트에서 호출되는 경우 이전에 환경을 설정해야합니다 (vcvarsall.bat 한 번 실행 및 / 또는 MS C ++ 2010 및 mspdb100.dll 참조). ).


2
default rel파일 상단에서 사용하여 주소 지정 모드 ( [msg][title])가 32 비트 절대 주소 대신 RIP 상대 주소 지정을 사용 하도록 적극 권장 합니다 .
Peter Cordes 19

연결 방법을 설명 해주셔서 감사합니다! 당신은 내 정신 건강을 구했습니다. 나는 '오류 LNK2001 : 해결되지 않은 외부 기호 ExitProcess'및 유사한 오류 때문에 머리카락을 꺼내기 시작했습니다.
Nik

5

어떤 함수 를 호출하지 않는 한 이것은 전혀 사소한 것이 아닙니다. (그리고 진지하게, printf를 호출하는 것과 win32 api 함수를 호출하는 것 사이의 복잡성에는 실제 차이가 없습니다.)

DOS int 21h조차도 API가 다르더라도 실제로는 함수 호출 일뿐입니다.

도움없이 수행하려면 비디오 하드웨어와 직접 대화해야합니다. "Hello world"문자의 비트 맵을 프레임 버퍼에 기록 할 수 있습니다. 그럼에도 불구하고 비디오 카드는 이러한 메모리 값을 VGA / DVI 신호로 변환하는 작업을 수행합니다.

하드웨어에 이르기까지이 모든 것들이 C에서보다 ASM에서 더 흥미롭지 않다는 점에 유의하십시오. "hello world"프로그램은 함수 호출로 귀결됩니다. ASM의 한 가지 좋은 점은 원하는 모든 ABI를 매우 쉽게 사용할 수 있다는 것입니다. ABI가 무엇인지 알아야합니다.


이것은 훌륭한 포인트입니다 --- ASM과 C는 둘 다 OS 제공 기능 (Windows의 _WriteFile)에 의존합니다. 그렇다면 마법은 어디에 있습니까? 비디오 카드의 장치 드라이버 코드에 있습니다.
Assad Ebrahim

2
이것은 철저히 요점을 벗어납니다. 포스터는 "Windows에서"실행되는 어셈블러 프로그램을 요구합니다. 즉, Windows 기능 (예 : kernel32.dll)을 사용할 수 있지만 Cygwin의 libc와 같은 다른 기능은 사용할 수 없습니다. 큰 소리로 울기 위해 포스터에는 C 라이브러리가 없다고 명시되어 있습니다.
Albert van der Horst

5

가장 좋은 예는 fasm을 사용하는 것입니다. fasm은 다른 불투명 한 복잡성 계층에 의해 Windows 프로그래밍의 복잡성을 숨기는 링커를 사용하지 않기 때문입니다. GUI 창에 쓰는 프로그램에 만족한다면 fasm의 예제 디렉토리에 이에 대한 예제가 있습니다.

콘솔 프로그램을 원하면 표준 입력 및 표준 출력의 리디렉션도 가능합니다. GUI를 사용하지 않고 콘솔에서 엄격하게 작동하는 (매우 사소하지 않은) 예제 프로그램이 있습니다. 즉 fasm 그 자체입니다. 이것은 필수 요소로 얇아 질 수 있습니다. (나는 또 다른 비 GUI 예제 인 네 번째 컴파일러를 작성했지만 사소한 것도 아닙니다).

이러한 프로그램에는 일반적으로 링커가 수행하는 32 비트 실행 파일에 대한 적절한 헤더를 생성하는 다음 명령이 있습니다.

FORMAT PE CONSOLE 

'.idata'라는 섹션에는 시작하는 동안 창에서 함수 이름을 런타임 주소에 연결하는 데 도움이되는 테이블이 포함되어 있습니다. 또한 Windows 운영 체제 인 KERNEL.DLL에 대한 참조도 포함되어 있습니다.

 section '.idata' import data readable writeable
    dd 0,0,0,rva kernel_name,rva kernel_table
    dd 0,0,0,0,0

  kernel_table:
    _ExitProcess@4    DD rva _ExitProcess
    CreateFile        DD rva _CreateFileA
        ...
        ...
    _GetStdHandle@4   DD rva _GetStdHandle
                      DD 0

테이블 형식은 창에 의해 지정되며 프로그램이 시작될 때 시스템 파일에서 조회되는 이름을 포함합니다. FASM은 rva 키워드의 복잡성을 일부 숨 깁니다. 따라서 _ExitProcess @ 4는 fasm 레이블이고 _exitProcess는 Windows에서 조회하는 문자열입니다.

프로그램은 '.text'섹션에 있습니다. 해당 섹션을 읽기 가능하고 쓰기 가능하며 실행 가능하다고 선언하는 경우 추가해야하는 유일한 섹션입니다.

    section '.text' code executable readable writable

.idata 섹션에서 선언 한 모든 기능을 호출 할 수 있습니다. 콘솔 프로그램의 경우 표준 입력 및 표준 출력에 대한 파일 설명자를 찾으려면 _GetStdHandle이 필요합니다 (fasm이 포함 파일 win32a.inc에서 찾는 STD_INPUT_HANDLE과 같은 기호 이름 사용). 파일 설명자가 있으면 WriteFile 및 ReadFile을 수행 할 수 있습니다. 모든 기능은 kernel32 문서에 설명되어 있습니다. 당신은 아마 그것을 알고 있거나 어셈블러 프로그래밍을 시도하지 않을 것입니다.

요약 : Windows OS에 결합되는 asci 이름이있는 테이블이 있습니다. 시작하는 동안 이것은 프로그램에서 사용하는 호출 가능한 주소 테이블로 변환됩니다.


FASM은 링커를 사용할 수 없지만 여전히 PE 파일을 어셈블해야합니다. 즉, 실제로 코드를 조립하는 것이 아니라 링커가 일반적으로 수행하는 작업을 자체적으로 수행합니다. 따라서 제 생각에는 링커가 "복잡성을 숨기는 것"이 ​​없다고 오해하는 것입니다. -어셈블러가하는 일은 프로그램을 어셈블하는 것이지만 링커에 맡겨서 프로그램을 많은 것에 의존 할 수있는 프로그램 이미지에 임베드합니다. 따라서 링커와 어셈블러를 분리하는 것이 좋은 일이라고 생각합니다.
AMN

@amn 이렇게 생각해보세요. 링커를 사용하여 위의 프로그램을 만드는 경우 프로그램이 수행하는 작업 또는 구성 요소에 대한 더 많은 통찰력을 제공합니까? fasm 소스를 보면 프로그램의 전체 구조를 알고 있습니다.
Albert van der Horst

공정한 포인트. 반대로 다른 모든 것과 연결을 분리하는 것도 장점이 있습니다. 일반적으로 개체 파일에 대한 액세스 권한이 있습니다 (프로그램 이미지 파일 형식과 관계없이 프로그램의 구조를 검사하는 데 큰 도움이 됨). 다른 옵션을 사용하여 원하는 다른 링커를 호출 할 수 있습니다. 재사용 가능성과 구성 가능성에 관한 것입니다. 이를 염두에두고 FASM은 "편리"하기 때문에 모든 작업을 수행하는 것이 이러한 원칙을 위반합니다. 나는 원칙적으로 그것에 반대하는 것이 아닙니다. 나는 그것에 대한 정당성을 봅니다. 그러나 나는 그것을 필요로하지 않습니다.
오전

fasm 64 비트 창의 맨 윗줄에 불법적 인 isntruction에 대한 오류가 발생합니다.
bluejayke

@bluejayke 아마 당신은 fasm에 대한 문서를 가지고 있지 않았을 것입니다. FORMAT PE는 32 비트 실행 파일을 생성하며 64 비트 창은 실행을 거부합니다. 64 비트 프로그램의 경우 FORMAT PE64를 원합니다. 또한 프로그램에서 적절한 64 비트 명령어를 사용하는지 확인하십시오.
Albert van der Horst

3

anderstornvig의 Hello World 예제와 함께 NASM 및 Visual Studio의 링커 (link.exe)를 사용하려면 printf () 함수가 포함 된 C 런타임 라이브러리에 수동으로 연결해야합니다.

nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib

이것이 누군가를 돕기를 바랍니다.


질문의 포스터는 Windows가 제공하는 기능을 기반으로 누군가가 printf를 작성하는 방법을 알고 싶어합니다.
Albert van der Horst
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.