Windows에서 어셈블리에 기본적인 것을 작성하고 싶었는데 NASM을 사용하고 있지만 아무것도 작동하지 않습니다.
Windows에서 C 함수의 도움없이 Hello World를 작성하고 컴파일하는 방법은 무엇입니까?
Windows에서 어셈블리에 기본적인 것을 작성하고 싶었는데 NASM을 사용하고 있지만 아무것도 작동하지 않습니다.
Windows에서 C 함수의 도움없이 Hello World를 작성하고 컴파일하는 방법은 무엇입니까?
답변:
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
하여 모든 세그먼트 레지스터가 서로 동일한 상태로 로드되도록합니다 (작은 메모리 모델).
행운을 빕니다.
이 예제는 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
gcc hello.obj
Windows API 호출을 사용하는 Win32 및 Win64 예제입니다. NASM이 아닌 MASM을위한 것이지만 살펴보십시오. 이 기사 에서 자세한 내용을 찾을 수 있습니다 .
이것은 stdout에 인쇄하는 대신 MessageBox를 사용합니다.
;---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
;---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을 배웁니다. invoke
for stdcall
또는 fastcall
while cinvoke
은 for cdecl
또는 변수 인수 fastcall
입니다. 어셈블러는 사용할 항목을 알고 있습니다.
출력을 분해하여 invoke
확장 된 방식을 볼 수 있습니다 .
title
레이블 이름으로 사용할 때 오류가 발생합니다. 그러나 같은 레이블 이름으로 다른 것을 사용 mytitle
하면 모든 것이 잘 작동합니다.
플랫 어셈블러 에는 추가 링커가 필요하지 않습니다. 이것은 어셈블러 프로그래밍을 매우 쉽게 만듭니다. Linux에서도 사용할 수 있습니다.
이것은 hello.asm
Fasm 예제에서 가져온 것입니다.
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
.
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_link 는 C : \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin 이거나 컴퓨터의 link.exe 프로그램이있는 위치에 있을 수 있으며 path_to_libs 는 C : \ 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 참조). ).
default rel
파일 상단에서 사용하여 주소 지정 모드 ( [msg]
및 [title]
)가 32 비트 절대 주소 대신 RIP 상대 주소 지정을 사용 하도록 적극 권장 합니다 .
어떤 함수 를 호출하지 않는 한 이것은 전혀 사소한 것이 아닙니다. (그리고 진지하게, printf를 호출하는 것과 win32 api 함수를 호출하는 것 사이의 복잡성에는 실제 차이가 없습니다.)
DOS int 21h조차도 API가 다르더라도 실제로는 함수 호출 일뿐입니다.
도움없이 수행하려면 비디오 하드웨어와 직접 대화해야합니다. "Hello world"문자의 비트 맵을 프레임 버퍼에 기록 할 수 있습니다. 그럼에도 불구하고 비디오 카드는 이러한 메모리 값을 VGA / DVI 신호로 변환하는 작업을 수행합니다.
하드웨어에 이르기까지이 모든 것들이 C에서보다 ASM에서 더 흥미롭지 않다는 점에 유의하십시오. "hello world"프로그램은 함수 호출로 귀결됩니다. ASM의 한 가지 좋은 점은 원하는 모든 ABI를 매우 쉽게 사용할 수 있다는 것입니다. ABI가 무엇인지 알아야합니다.
가장 좋은 예는 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 이름이있는 테이블이 있습니다. 시작하는 동안 이것은 프로그램에서 사용하는 호출 가능한 주소 테이블로 변환됩니다.
anderstornvig의 Hello World 예제와 함께 NASM 및 Visual Studio의 링커 (link.exe)를 사용하려면 printf () 함수가 포함 된 C 런타임 라이브러리에 수동으로 연결해야합니다.
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
이것이 누군가를 돕기를 바랍니다.