Tôi muốn viết một cái gì đó cơ bản trong lắp ráp trong Windows, tôi đang sử dụng NASM, nhưng tôi không thể làm bất cứ điều gì hoạt động.
Làm thế nào để viết và biên dịch hello world mà không cần sự trợ giúp của các hàm C trên Windows?
Tôi muốn viết một cái gì đó cơ bản trong lắp ráp trong Windows, tôi đang sử dụng NASM, nhưng tôi không thể làm bất cứ điều gì hoạt động.
Làm thế nào để viết và biên dịch hello world mà không cần sự trợ giúp của các hàm C trên Windows?
Câu trả lời:
Đang gọi libc stdio printf
, đang triển khaiint 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
Sau đó chạy
nasm -fwin32 helloworld.asm
gcc helloworld.obj
a
Ngoài ra còn có The Clueless Newbies Guide to Hello World in Nasm mà không cần sử dụng thư viện C. Sau đó, mã sẽ giống như thế này.
Mã 16 bit với lệnh gọi hệ thống MS-DOS: hoạt động trong trình giả lập DOS hoặc trong Windows 32 bit có hỗ trợ NTVDM . Không thể chạy "trực tiếp" (trong suốt) trong bất kỳ Windows 64-bit nào vì nhân x86-64 không thể sử dụng chế độ vm86.
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
Xây dựng điều này thành một .com
tệp thực thi để nó sẽ được tải cs:100h
với tất cả các thanh ghi phân đoạn bằng nhau (mô hình bộ nhớ nhỏ).
Chúc may mắn.
Ví dụ này cho thấy cách truy cập trực tiếp vào API Windows và không liên kết trong Thư viện tiêu chuẩn C.
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:
Để biên dịch, bạn sẽ cần NASM và LINK.EXE (từ Visual studio Standard Edition)
nasm -fwin32 hello.asm liên kết / hệ thống con: console / nodefaultlib / entry: main hello.obj
gcc hello.obj
Đây là các ví dụ Win32 và Win64 sử dụng lệnh gọi API Windows. Chúng dành cho MASM chứ không phải NASM, nhưng hãy xem chúng. Bạn có thể tìm thêm chi tiết trong này bài viết.
Điều này sử dụng MessageBox thay vì in ra 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
Để lắp ráp và liên kết chúng bằng MASM, hãy sử dụng cái này cho tệp thực thi 32 bit:
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
hoặc cái này cho 64-bit thực thi:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
Tại sao Windows x64 cần dự trữ 28h byte không gian ngăn xếp trước a call
? Đó là 32 byte (0x20) của không gian bóng hay còn gọi là không gian gia đình, theo yêu cầu của quy ước gọi. Và 8 byte khác để căn chỉnh lại ngăn xếp bằng 16, vì quy ước gọi yêu cầu RSP phải được căn chỉnh 16 byte trước a call
. ( main
Người gọi của chúng tôi (trong mã khởi động CRT) đã làm điều đó. Địa chỉ trả về 8 byte có nghĩa là RSP cách 8 byte từ ranh giới 16 byte khi nhập một hàm.)
Không gian bóng có thể được sử dụng bởi một hàm để kết xuất các args thanh ghi của nó bên cạnh vị trí của bất kỳ args ngăn xếp nào (nếu có). A system call
yêu cầu 30h (48 byte) cũng dành không gian cho r10 và r11 ngoài 4 thanh ghi đã đề cập trước đó. Nhưng các cuộc gọi DLL chỉ là các cuộc gọi hàm, ngay cả khi chúng bao bọc xung quanh các syscall
hướng dẫn.
Thực tế thú vị: không phải Windows, tức là quy ước gọi x86-64 System V (ví dụ: trên Linux) hoàn toàn không sử dụng không gian bóng và sử dụng tối đa 6 args số nguyên / con trỏ và tối đa 8 args FP trong thanh ghi XMM .
Sử dụng invoke
chỉ thị của MASM (biết quy ước gọi), bạn có thể sử dụng một ifdef để tạo một phiên bản của ifdef này có thể được xây dựng dưới dạng 32 bit hoặc 64 bit.
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
Biến thể macro giống nhau cho cả hai, nhưng bạn sẽ không học lắp ráp theo cách này. Thay vào đó, bạn sẽ học asm kiểu C. invoke
là for stdcall
hoặc fastcall
while cinvoke
là đối số for cdecl
hoặc biến fastcall
. Trình lắp ráp biết cách sử dụng.
Bạn có thể tháo rời đầu ra để xem mức độ invoke
mở rộng.
title
làm tên nhãn, tôi gặp lỗi. Tuy nhiên, khi tôi sử dụng một cái gì đó khác làm tên nhãn mytitle
, mọi thứ hoạt động tốt.
Flat Assembler không cần thêm một trình liên kết. Điều này làm cho việc lập trình trình hợp dịch trở nên khá dễ dàng. Nó cũng có sẵn cho Linux.
Đây là hello.asm
từ các ví dụ 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 tạo ra một tệp thực thi:
> fasm hello.asm phiên bản trình lắp ráp phẳng 1.70.03 (bộ nhớ 1048575 kilobyte) 4 lần, 1536 byte.
Và đây là chương trình trong IDA :
Bạn sẽ nhìn thấy ba cuộc gọi: GetCommandLine
, MessageBox
và ExitProcess
.
Để có được .exe với NASM'compiler và trình liên kết của Visual Studio, mã này hoạt động tốt:
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
Nếu mã này được lưu trên ví dụ "test64.asm", thì để biên dịch:
nasm -f win64 test64.asm
Tạo "test64.obj" Sau đó để liên kết từ dấu nhắc lệnh:
path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no
trong đó path_to_link có thể là C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin hoặc bất cứ nơi nào là chương trình link.exe trong máy của bạn, path_to_libs có thể là C: \ Program Files (x86) \ Windows Kits \ 8.1 \ Lib \ winv6.3 \ um \ x64 hoặc thư viện của bạn ở bất kỳ đâu (trong trường hợp này, cả kernel32.lib và user32.lib đều nằm trên cùng một nơi, nếu không, hãy sử dụng một tùy chọn cho mỗi đường dẫn bạn cần) và / ambeaddressaware: không có tùy chọn nào cần thiết để tránh phàn nàn của người liên kết về địa chỉ dài (đối với user32.lib trong trường hợp này). Ngoài ra, như được thực hiện ở đây, nếu trình liên kết của Visual được gọi từ dấu nhắc lệnh, cần thiết lập môi trường trước đó (chạy một lần vcvarsall.bat và / hoặc xem MS C ++ 2010 và mspdb100.dll).
default rel
ở đầu tệp của mình để các chế độ địa chỉ đó ( [msg]
và [title]
) sử dụng địa chỉ tương đối RIP thay vì tuyệt đối 32 bit.
Trừ khi bạn gọi một số chức năng, điều này không hề tầm thường chút nào. (Và, nghiêm túc, không có sự khác biệt thực sự về độ phức tạp giữa việc gọi printf và gọi một hàm win32 api.)
Ngay cả DOS int 21h thực sự chỉ là một lời gọi hàm, ngay cả khi nó là một API khác.
Nếu bạn muốn làm điều đó mà không cần trợ giúp, bạn cần phải nói chuyện trực tiếp với phần cứng video của mình, có khả năng ghi bitmap của các chữ cái của "Hello world" vào bộ đệm khung. Ngay cả khi đó card màn hình vẫn thực hiện công việc dịch các giá trị bộ nhớ đó thành tín hiệu VGA / DVI.
Lưu ý rằng, thực sự, không có thứ nào trong số này đi xuống phần cứng thú vị hơn trong ASM hơn là trong C. Một chương trình "hello world" kết hợp với một lệnh gọi hàm. Một điều thú vị về ASM là bạn có thể sử dụng bất kỳ ABI nào bạn muốn khá dễ dàng; bạn chỉ cần biết ABI đó là gì.
Ví dụ tốt nhất là những người có fasm, bởi vì fasm không sử dụng trình liên kết, điều này che giấu sự phức tạp của lập trình windows bằng một lớp phức tạp khác. Nếu bạn hài lòng với một chương trình ghi vào cửa sổ gui, thì sẽ có một ví dụ cho chương trình đó trong thư mục ví dụ của fasm.
Nếu bạn muốn một chương trình console, điều đó cho phép chuyển hướng tiêu chuẩn vào và tiêu chuẩn ra cũng có thể. Có một chương trình ví dụ (helas cao không tầm thường) có sẵn không sử dụng gui và hoạt động nghiêm ngặt với bảng điều khiển, đó là chính nó. Điều này có thể được làm mỏng cho những điều cần thiết. (Tôi đã viết một trình biên dịch thứ tư là một ví dụ khác không phải gui, nhưng nó cũng không tầm thường).
Một chương trình như vậy có lệnh sau đây để tạo một tiêu đề thích hợp cho 32-bit thực thi, thường được thực hiện bởi một trình liên kết.
FORMAT PE CONSOLE
Một phần được gọi là '.idata' chứa một bảng giúp các cửa sổ trong quá trình khởi động ghép nối tên của các hàm với địa chỉ thời gian chạy. Nó cũng chứa một tham chiếu đến KERNEL.DLL là Hệ điều hành 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
Định dạng bảng do cửa sổ áp đặt và chứa các tên được tra cứu trong tệp hệ thống, khi chương trình được khởi động. FASM ẩn một số phức tạp đằng sau từ khóa rva. Vì vậy, _ExitProcess @ 4 là một nhãn fasm và _exitProcess là một chuỗi được Windows tra cứu.
Chương trình của bạn nằm trong phần '.text'. Nếu bạn khai báo rằng phần có thể đọc được và có thể thực thi được, thì đó là phần duy nhất bạn cần thêm.
section '.text' code executable readable writable
Bạn có thể gọi tất cả các cơ sở mà bạn đã khai báo trong phần .idata. Đối với một chương trình bảng điều khiển, bạn cần _GetStdHandle để tìm anh ta đã phân loại các bộ ký hiệu cho chuẩn trong và chuẩn (sử dụng các tên tượng trưng như STD_INPUT_HANDLE mà fasm tìm thấy trong tệp bao gồm win32a.inc). Khi bạn đã có bộ mô tả tệp, bạn có thể thực hiện WriteFile và ReadFile. Tất cả các chức năng được mô tả trong tài liệu kernel32. Bạn có thể biết điều đó hoặc bạn sẽ không thử lập trình trình hợp dịch.
Tóm lại: Có một bảng với các tên asci phù hợp với hệ điều hành windows. Trong quá trình khởi động, bảng này được chuyển đổi thành một bảng các địa chỉ có thể gọi mà bạn sử dụng trong chương trình của mình.
Nếu bạn muốn sử dụng trình liên kết của NASM và Visual Studio (link.exe) với ví dụ Hello World của anderstornvig, bạn sẽ phải liên kết thủ công với C Runtime Libary có chứa hàm printf ().
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
Hy vọng điều này sẽ giúp ai đó.