Làm thế nào để viết hello world trong trình hợp ngữ dưới Windows?


94

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?


3
Ngoài ra, hãy xem bộ bắt đầu lắp ráp cửa sổ Small Is Beautiful của Steve Gibson .
Jeremy

Không sử dụng thư viện c là một hạn chế hơi kỳ lạ. Người ta phải gọi một số thư viện trong hệ điều hành MS-Windows. Có lẽ là kernel32.dll. Cho dù Microsoft đã viết điều này bằng c hay Pascal có vẻ không liên quan. Có nghĩa là chỉ các chức năng do hệ điều hành cung cấp mới có thể được gọi, những gì trong hệ thống kiểu Unix sẽ được gọi là lệnh gọi hệ thống?
Albert van der Horst,

Với thư viện C, tôi cho rằng anh ấy hoặc cô ấy có nghĩa là không sử dụng thư viện thời gian chạy C như những thư viện đi kèm với GCC hoặc MSVC. Tất nhiên người đó sẽ phải sử dụng một số tệp DLL tiêu chuẩn của Windows, như kernel32.dll.
Rudy Velthuis

2
Sự khác biệt giữa kernel32.dll và thư viện thời gian chạy gcc không phải ở định dạng (cả hai đều là dll) và không phải trong ngôn ngữ (cả hai có thể là c, nhưng điều đó bị ẩn.) Sự khác biệt là giữa hệ điều hành cung cấp hoặc không.
Albert van der Horst

Tôi đã tìm kiếm điều này cũng lol không thể tìm thấy bất cứ điều gì với cảm xúc mà không có bao gồm
bluejayke

Câu trả lời:


39

Ví dụ về NASM .

Đ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 .comtệp thực thi để nó sẽ được tải cs:100hvớ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.


28
Câu hỏi đặt ra đề cập rõ ràng "mà không sử dụng các thư viện C"
Mehrdad Afshari

25
Sai lầm. Bản thân thư viện C rõ ràng có thể, vì vậy nó có thể. Trên thực tế, nó chỉ hơi khó hơn một chút. Bạn chỉ cần gọi WriteConsole () với đúng 5 tham số.
MSalters

12
Mặc dù ví dụ thứ hai không gọi bất kỳ hàm thư viện C nào, nó cũng không phải là chương trình Windows. Máy DOS ảo sẽ được kích hoạt để chạy nó.
Rômulo Ceccon

7
@Alex Hart, ví dụ thứ hai của anh ấy là dành cho DOS, không phải cho Windows. Trong DOS, các chương trình ở chế độ nhỏ (tệp .COM, tổng mã dưới 64Kb + dữ liệu + ngăn xếp) bắt đầu ở 0x100h vì 256 byte đầu tiên trong phân đoạn được PSP thực hiện (args dòng lệnh, v.v.). Xem liên kết này: en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov

7
Đây không phải là những gì được yêu cầu. Ví dụ đầu tiên sử dụng thư viện C và ví dụ thứ hai là MS-DOS, không phải Windows.
Paulo Pinto

131

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 

21
bạn có thể cần phải bao gồm kernel32.lib để liên kết điều này (tôi đã làm). liên kết / hệ thống phụ: giao diện điều khiển / nodefaultlib / entry: chính hello.obj kernel32.lib
Zach Burlingame

5
Làm thế nào để liên kết obj với ld.exe từ MinGW?
DarrenVortex

4
@DarrenVortexgcc hello.obj
towry 10/1013

4
Điều này cũng sẽ hoạt động khi sử dụng các trình liên kết miễn phí như Alink từ sourceforge.net/projects/alink hoặc GoLink từ godevtool.com/#linker ? Tôi không muốn cài đặt visual studio chỉ cho điều đó?
jj_

21

Đâ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.

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

Để 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. ( mainNgườ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 callyê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 syscallhướ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ỏ tối đa 8 args FP trong thanh ghi XMM .


Sử dụng invokechỉ 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. invokelà for stdcallhoặc fastcallwhile cinvokelà đối số for cdeclhoặ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 độ invokemở rộng.


1
+1 cho câu trả lời của bạn. Bạn có thể vui lòng thêm mã lắp ráp cho Windows trên ARM (WOA) không?
Annie

1
Tại sao rsp yêu cầu 0x28 byte chứ không phải 0x20? Tất cả các tham chiếu về quy ước gọi nói rằng nó phải là 32 nhưng nó dường như yêu cầu 40 trong thực tế.
douggard 11/1017

Trong mã hộp thư 32-bit của bạn, vì một số lý do khi tôi sử dụng titlelà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.
user3405291 14/03/18

làm thế nào để làm điều đó với không bao gồm?
bluejayke

14

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.asmtừ 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 :

nhập mô tả hình ảnh ở đây

Bạn sẽ nhìn thấy ba cuộc gọi: GetCommandLine, MessageBoxExitProcess.


điều này sử dụng bao gồm và GUI làm thế nào để chúng tôi làm điều đó chỉ với CMD mà không có bao gồm cả?
bluejayke

Cố gắng đọc hướng dẫn sử dụng? flatassembler.net/docs.php?article=manual#2.4.2
thúc

bạn có thể chỉ cho tôi một phần ghi vào bảng điều khiển mà không có bất kỳ hình nền nào không?
bluejayke

12

Để 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).


2
Tôi thực sự khuyên bạn nên sử dụng default relở đầu tệp của mình để các chế độ địa chỉ đó ( [msg][title]) sử dụng địa chỉ tương đối RIP thay vì tuyệt đối 32 bit.
Peter Cordes

Cảm ơn bạn đã giải thích cách liên kết! Bạn đã cứu sức khỏe tinh thần của tôi. Tôi đã bắt đầu bứt tóc vì 'lỗi LNK2001: ký hiệu bên ngoài chưa được giải quyết ExitProcess' và các lỗi tương tự ...
Nik

5

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ì.


Đây là một điểm tuyệt vời --- ASM và C đều dựa trên một chức năng do hệ điều hành cung cấp (_WriteFile trong Windows). Vậy điều kỳ diệu nằm ở đâu? Nó nằm trong mã trình điều khiển thiết bị cho card màn hình.
Assad Ebrahim

2
Điều này là triệt để bên cạnh điểm. Người đăng yêu cầu một chương trình hợp ngữ chạy "trong Windows". Điều đó có nghĩa là có thể sử dụng các tiện ích Windows (ví dụ: kernel32.dll), nhưng không sử dụng các tiện ích khác như libc dưới Cygwin. Vì đã khóc to, người đăng bài nói rõ ràng không có thư viện c.
Albert van der Horst

5

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.


FASM có thể không sử dụng trình liên kết nhưng nó vẫn phải tập hợp một tệp PE. Điều đó có nghĩa là nó thực sự không chỉ lắp ráp mã mà còn tự đảm nhận một công việc bình thường mà trình liên kết sẽ thực hiện, và như vậy, theo ý kiến ​​khiêm tốn của tôi, nó gây hiểu lầm khi gọi absense của trình liên kết là "ẩn sự phức tạp", hoàn toàn ngược lại - công việc của một trình hợp dịch là lắp ráp một chương trình, nhưng để trình liên kết nhúng chương trình vào một hình ảnh chương trình có thể phụ thuộc vào rất nhiều thứ. Như vậy, tôi thấy sự tách biệt giữa trình liên kết và trình hợp ngữ là một điều tốt , bạn không đồng ý với điều này.
Amn

@amn Hãy nghĩ về nó theo cách này. Nếu bạn sử dụng một trình liên kết để tạo chương trình trên, nó có cung cấp cho bạn cái nhìn sâu sắc hơn về những gì chương trình làm hoặc nó bao gồm những gì? Nếu tôi nhìn vào nguồn fasm, tôi biết cấu trúc hoàn chỉnh của chương trình.
Albert van der Horst

Điểm công bằng. Mặt khác, tách liên kết khỏi mọi thứ khác cũng có lợi ích của nó. Bạn thường có quyền truy cập vào một tệp đối tượng (điều này sẽ giúp bạn kiểm tra cấu trúc của chương trình, độc lập với định dạng tệp hình ảnh của chương trình), bạn có thể gọi một trình liên kết khác theo sở thích của mình, với các tùy chọn khác nhau. Đó là về khả năng tái sử dụng và khả năng phối ghép. Với ý nghĩ đó, FASM làm mọi thứ vì nó "tiện lợi" phá vỡ những nguyên tắc đó. Về cơ bản, tôi không chống lại nó - tôi thấy họ biện minh cho điều đó - nhưng tôi, vì một lẽ, không cần nó.
Amn

nhận được lỗi cho isntruction bất hợp pháp ở dòng trên cùng trong cửa sổ 64 bit
Fasm

@bluejayke Có thể là bạn không có tài liệu về cơn sốt trong tay. FORMAT PE tạo ra một tệp thực thi 32 bit, mà các cửa sổ 64 bit từ chối chạy. Đối với chương trình 64 bit, bạn muốn ĐỊNH DẠNG PE64. Đồng thời đảm bảo rằng bạn sử dụng hướng dẫn 64 bit thích hợp trong chương trình của mình.
Albert van der Horst

3

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 đó.


Người đăng các câu hỏi muốn biết, ai đó sẽ viết printf như thế nào dựa trên các tiện ích mà Windows cung cấp, vì vậy điều này một lần nữa hoàn toàn không có lợi.
Albert van der Horst
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.