ฉันต้องการเขียนสิ่งพื้นฐานในการประกอบใน Windows ฉันใช้ NASM แต่ฉันไม่สามารถทำงานอะไรได้เลย
จะเขียนและรวบรวมสวัสดีชาวโลกโดยไม่ใช้ฟังก์ชัน C บน Windows ได้อย่างไร?
ฉันต้องการเขียนสิ่งพื้นฐานในการประกอบใน Windows ฉันใช้ NASM แต่ฉันไม่สามารถทำงานอะไรได้เลย
จะเขียนและรวบรวมสวัสดีชาวโลกโดยไม่ใช้ฟังก์ชัน C บน Windows ได้อย่างไร?
คำตอบ:
ตัวอย่าง 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
นอกจากนี้ยังมีThe Clueless Newbies Guide to Hello World in Nasmโดยไม่ต้องใช้ไลบรารี C จากนั้นโค้ดจะมีลักษณะดังนี้
รหัส 16 บิตกับ MS-DOS สายระบบ: ผลงานในการเลียนแบบ DOS หรือใน Windows 32 บิตด้วยการสนับสนุน ไม่สามารถเรียกใช้ "โดยตรง" (โปร่งใส) ภายใต้ Windows 64 บิตใด ๆ เนื่องจากเคอร์เนล x86-64 ไม่สามารถใช้โหมด vm86
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
สร้างสิ่งนี้ให้เป็น.com
ไฟล์ปฏิบัติการดังนั้นจะถูกโหลดcs:100h
พร้อมกับการลงทะเบียนทุกส่วนที่เท่าเทียมกัน (แบบจำลองหน่วยความจำขนาดเล็ก)
โชคดี.
ตัวอย่างนี้แสดงวิธีไปที่ Windows API โดยตรงและไม่เชื่อมโยงใน C Standard Library
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 สวัสดีครับ ลิงค์ / ระบบย่อย: console / nodefaultlib / entry: main hello.obj
gcc hello.obj
นี่คือตัวอย่าง Win32 และ Win64 โดยใช้การเรียก Windows API พวกเขามีไว้สำหรับ MASM มากกว่า NASM แต่ลองดูที่พวกเขา คุณสามารถค้นหารายละเอียดเพิ่มเติมในนี้บทความ
สิ่งนี้ใช้ MessageBox แทนการพิมพ์ไปยัง stdout
;---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 จึงต้องจองพื้นที่สแต็ก 28 ชม. ก่อน a call
? นั่นคือพื้นที่เงา 32 ไบต์ (0x20) หรือที่เรียกว่าพื้นที่บ้านตามที่กำหนดไว้ในรูปแบบการเรียก และอีก 8 ไบต์ใหม่จัดสแต็ค 16 เพราะการประชุมเรียกร้องต้องถูก RSP 16 ไบต์ชิดก่อน call
( main
ผู้โทรของเรา(ในรหัสเริ่มต้น CRT) ทำเช่นนั้นที่อยู่ส่งคืน 8 ไบต์หมายความว่า RSP อยู่ห่างจากขอบเขต 16 ไบต์ 8 ไบต์เมื่อเข้าสู่ฟังก์ชัน)
พื้นที่เงาสามารถใช้โดยฟังก์ชันเพื่อดัมพ์อาร์เรย์รีจิสเตอร์ที่อยู่ถัดจากตำแหน่งที่สแต็กอาร์ก (ถ้ามี) จะอยู่ A system call
ต้องการ 30 ชม. (48 ไบต์) เพื่อสำรองพื้นที่สำหรับ r10 และ r11 นอกเหนือจากการลงทะเบียน 4 รายการที่กล่าวถึงก่อนหน้านี้ แต่การเรียก DLL เป็นเพียงการเรียกใช้ฟังก์ชันแม้ว่าจะเป็นการเรียกใช้syscall
คำสั่งก็ตาม
ข้อเท็จจริงที่น่าสนใจ: ไม่ใช่ Windows เช่นการเรียกใช้ x86-64 System V (เช่นบน Linux) ไม่ได้ใช้พื้นที่เงาเลยและใช้อาร์เรย์รีจิสเตอร์จำนวนเต็ม / ตัวชี้ได้สูงสุด 6 รายการและอาร์เรย์ FP สูงสุด 8 รายการในการลงทะเบียน XMM .
การใช้invoke
คำสั่งของ MASM (ซึ่งรู้หลักการเรียก) คุณสามารถใช้ 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
ตัวแปรมาโครเหมือนกันสำหรับทั้งคู่ แต่คุณจะไม่ได้เรียนรู้การประกอบด้วยวิธีนี้ คุณจะได้เรียนรู้ asm สไตล์ C แทน invoke
สำหรับstdcall
หรือfastcall
ในขณะที่cinvoke
สำหรับการหรืออาร์กิวเมนต์ตัวแปรcdecl
fastcall
แอสเซมเบลอร์รู้ว่าต้องใช้อะไร
คุณสามารถแยกชิ้นส่วนเอาต์พุตเพื่อดูว่าinvoke
ขยายได้อย่างไร
title
เป็นชื่อป้ายกำกับฉันพบข้อผิดพลาด อย่างไรก็ตามเมื่อฉันใช้อย่างอื่นเป็นชื่อป้ายกำกับmytitle
ทุกอย่างก็ใช้ได้ดี
Flat Assemblerไม่จำเป็นต้องมีตัวเชื่อมพิเศษ ทำให้การเขียนโปรแกรมแอสเซมเบลอร์ทำได้ง่ายมาก นอกจากนี้ยังสามารถใช้ได้กับ 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 สวัสดีครับ แอสเซมเบลอร์แบบแบนเวอร์ชัน 1.70.03 (หน่วยความจำ 1048575 กิโลไบต์) 4 รอบ 1536 ไบต์
และนี่คือโปรแกรมในIDA :
ท่านสามารถเข้าดูสามโทร: GetCommandLine
, และMessageBox
ExitProcess
ในการรับ. exe ด้วย NASM'compiler และตัวเชื่อมโยงของ Visual Studio รหัสนี้ใช้ได้ดี:
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: ไม่มีตัวเลือกคือ จำเป็นเพื่อหลีกเลี่ยงการบ่นของผู้เชื่อมโยงเกี่ยวกับที่อยู่ที่ยาว (สำหรับ user32.lib ในกรณีนี้) นอกจากนี้เมื่อทำเสร็จแล้วที่นี่หากตัวเชื่อมโยงของ Visual ถูกเรียกใช้จากพรอมต์คำสั่งจำเป็นต้องตั้งค่าสภาพแวดล้อมก่อนหน้านี้ (เรียกใช้ครั้งเดียว vcvarsall.bat และ / หรือดูMS C ++ 2010 และ mspdb100.dll).
default rel
ที่ด้านบนสุดของไฟล์ของคุณดังนั้นโหมดการกำหนดแอดเดรสเหล่านั้น ( [msg]
และ[title]
) ใช้การกำหนดแอดเดรสแบบสัมพันธ์ RIP แทนค่าสัมบูรณ์ 32 บิต
เว้นแต่คุณจะเรียกใช้ฟังก์ชันบางอย่างสิ่งนี้ก็ไม่สำคัญเลย (และอย่างจริงจังไม่มีความแตกต่างอย่างแท้จริงในความซับซ้อนระหว่างการเรียก printf และการเรียกใช้ฟังก์ชัน win32 api)
แม้แต่ DOS int 21h ก็เป็นเพียงการเรียกใช้ฟังก์ชันแม้ว่าจะเป็น API อื่นก็ตาม
หากคุณต้องการทำโดยไม่ได้รับความช่วยเหลือคุณจำเป็นต้องพูดคุยกับฮาร์ดแวร์วิดีโอของคุณโดยตรงให้เขียนบิตแมปของตัวอักษร "Hello world" ลงในเฟรมบัฟเฟอร์ ถึงแม้การ์ดแสดงผลจะทำงานในการแปลค่าหน่วยความจำเหล่านั้นเป็นสัญญาณ VGA / DVI
โปรดทราบว่าจริงๆแล้วไม่มีสิ่งใดเลยที่ลงไปถึงฮาร์ดแวร์ใน ASM จะน่าสนใจไปกว่าในโปรแกรม C. A "hello world" ที่เรียกใช้ฟังก์ชัน สิ่งที่ดีอย่างหนึ่งเกี่ยวกับ ASM คือคุณสามารถใช้ ABI ใดก็ได้ที่คุณต้องการค่อนข้างง่าย คุณแค่ต้องรู้ว่า ABI คืออะไร
ตัวอย่างที่ดีที่สุดคือตัวอย่างที่มี fasm เนื่องจาก fasm ไม่ใช้ตัวเชื่อมโยงซึ่งซ่อนความซับซ้อนของการเขียนโปรแกรม windows ด้วยความซับซ้อนที่ทึบแสงอื่น หากคุณพอใจกับโปรแกรมที่เขียนลงในหน้าต่าง gui มีตัวอย่างสำหรับสิ่งนั้นในไดเร็กทอรีตัวอย่างของ fasm
หากคุณต้องการโปรแกรมคอนโซลที่ช่วยให้สามารถเปลี่ยนเส้นทางของมาตรฐานในและมาตรฐานออกที่เป็นไปได้ มีโปรแกรมตัวอย่าง (helas สูงไม่สำคัญ) ที่ไม่ใช้ gui และทำงานร่วมกับคอนโซลได้อย่างเคร่งครัดนั่นคือ fasm เอง สิ่งนี้สามารถทำให้บางลงได้ตามความจำเป็น (ฉันได้เขียนคอมไพเลอร์ที่สี่ซึ่งเป็นอีกตัวอย่างที่ไม่ใช่ gui แต่ก็ไม่สำคัญเช่นกัน)
โปรแกรมดังกล่าวมีคำสั่งต่อไปนี้เพื่อสร้างส่วนหัวที่เหมาะสมสำหรับปฏิบัติการ 32 บิตโดยปกติแล้วตัวเชื่อมโยง
FORMAT PE CONSOLE
ส่วนที่เรียกว่า ".idata" มีตารางที่ช่วยให้หน้าต่างในระหว่างการเริ่มต้นใช้งานชื่อฟังก์ชันสองชื่อไปยังที่อยู่รันไทม์ นอกจากนี้ยังมีการอ้างอิงถึง KERNEL.DLL ซึ่งเป็นระบบปฏิบัติการ Windows
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
รูปแบบตารางถูกกำหนดโดย windows และมีชื่อที่ค้นหาในไฟล์ระบบเมื่อโปรแกรมเริ่มทำงาน FASM ซ่อนความซับซ้อนบางอย่างไว้เบื้องหลังคีย์เวิร์ด rva ดังนั้น _ExitProcess @ 4 จึงเป็น fasm label และ _exitProcess คือสตริงที่ Windows ค้นหา
โปรแกรมของคุณอยู่ในส่วน ".text" หากคุณประกาศว่าส่วนนั้นอ่านเขียนได้และปฏิบัติการได้นั่นเป็นส่วนเดียวที่คุณต้องเพิ่ม
section '.text' code executable readable writable
คุณสามารถเรียกสิ่งอำนวยความสะดวกทั้งหมดที่คุณประกาศไว้ในส่วน. idata สำหรับโปรแกรมคอนโซลคุณต้องมี _GetStdHandle เพื่อค้นหา filedescriptors สำหรับ standard in และ standardout (ใช้ชื่อสัญลักษณ์เช่น STD_INPUT_HANDLE ซึ่ง fasm พบในไฟล์ include win32a.inc) เมื่อคุณมีตัวอธิบายไฟล์แล้วคุณสามารถเขียน WriteFile และ ReadFile ได้ ฟังก์ชันทั้งหมดอธิบายไว้ในเอกสาร kernel32 คุณอาจจะทราบหรือไม่ลองเขียนโปรแกรมแอสเซมเบลอร์
โดยสรุป: มีตารางที่มีชื่อ asci ที่คู่กับ windows OS ในระหว่างการเริ่มต้นสิ่งนี้จะเปลี่ยนเป็นตารางที่อยู่ที่สามารถเรียกใช้ได้ซึ่งคุณใช้ในโปรแกรมของคุณ
หากคุณต้องการใช้ตัวเชื่อมโยง (link.exe) ของ NASM และ Visual Studio กับตัวอย่าง Hello World ของ anderstornvig คุณจะต้องเชื่อมโยงกับ C Runtime Libary ที่มีฟังก์ชัน printf () ด้วยตนเอง
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
หวังว่านี่จะช่วยใครบางคนได้