Bagaimana cara menulis hello world di assembler di bawah Windows?

94

Saya ingin menulis sesuatu yang mendasar dalam perakitan di bawah Windows, saya menggunakan NASM, tetapi saya tidak bisa mendapatkan apa pun yang berfungsi.

Bagaimana cara menulis dan mengkompilasi hello world tanpa bantuan fungsi C di Windows?

feiroox.dll
sumber
3
Lihat juga starter kit perakitan windows Small Is Beautiful milik Steve Gibson .
Jeremy
Tidak menggunakan pustaka-c adalah kendala yang agak aneh. Seseorang harus memanggil beberapa perpustakaan dalam sistem operasi MS-Windows. mungkin kernel32.dll. Apakah Microsoft telah menulis ini di c atau Pascal tampaknya tidak relevan. Apakah ini berarti bahwa hanya fungsi yang disediakan OS yang dapat dipanggil, apa dalam sistem tipe Unix yang disebut panggilan sistem?
Albert van der Horst
Dengan perpustakaan C saya berasumsi maksudnya tanpa menggunakan perpustakaan runtime C seperti yang disertakan dengan GCC atau MSVC. Tentu saja dia harus menggunakan beberapa DLL Windows standar, seperti kernel32.dll.
Rudy Velthuis
2
Perbedaan antara kernel32.dll dan pustaka runtime gcc tidak dalam format (keduanya dll) dan bukan dalam bahasanya (keduanya mungkin c, tetapi itu tersembunyi.) Perbedaannya adalah antara OS yang disediakan atau tidak.
Albert van der Horst
Saya telah mencari ini juga lol tidak dapat menemukan apa pun dengan fasm tanpa menyertakan
bluejayke

Jawaban:

39

Contoh NASM .

Memanggil libc stdio printf, mengimplementasikanint 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

Lalu lari

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

Ada juga The Clueless Newbies Guide to Hello World di Nasm tanpa menggunakan C library. Maka kodenya akan terlihat seperti ini.

Kode 16-bit dengan panggilan sistem MS-DOS: berfungsi di emulator DOS atau di Windows 32-bit dengan dukungan NTVDM . Tidak dapat dijalankan "secara langsung" (secara transparan) pada Windows 64-bit mana pun, karena kernel x86-64 tidak dapat menggunakan mode vm86.

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

Buat ini menjadi .comfile yang dapat dieksekusi sehingga akan dimuat cs:100hdengan semua register segmen sama satu sama lain (model memori kecil).

Semoga berhasil.

Peter Cordes
sumber
28
Pertanyaan tersebut secara eksplisit menyebutkan "tanpa menggunakan perpustakaan C"
Mehrdad Afshari
25
Salah. Perpustakaan C sendiri jelas bisa, jadi mungkin saja. Faktanya, ini hanya sedikit lebih sulit. Anda hanya perlu memanggil WriteConsole () dengan 5 parameter yang tepat.
MSalters
12
Meskipun contoh kedua tidak memanggil fungsi pustaka C, itu juga bukan program Windows. Mesin DOS Virtual akan diaktifkan untuk menjalankannya.
Rômulo Ceccon
7
@Alex Hart, contoh keduanya adalah untuk DOS, bukan untuk Windows. Dalam DOS, program dalam mode kecil (file .COM, di bawah 64Kb total kode + data + tumpukan) mulai dari 0x100h karena 256 byte pertama dalam segmen diambil oleh PSP (baris perintah args dll). Lihat tautan ini: en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov
7
Ini bukanlah yang diminta. Contoh pertama menggunakan pustaka C dan yang kedua adalah MS-DOS, bukan Windows.
Paulo Pinto
131

Contoh ini menunjukkan bagaimana untuk pergi langsung ke Windows API dan bukan link di 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:

Untuk mengkompilasi, Anda memerlukan NASM dan LINK.EXE (dari Visual studio Standard Edition)

   nasm -fwin32 hello.asm
   link / subsistem: konsol / nodefaultlib / entri: main hello.obj 
caffiend
sumber
21
Anda mungkin perlu menyertakan kernel32.lib untuk menautkan ini (saya lakukan). tautan / subsistem: konsol / nodefaultlib / entri: main hello.obj kernel32.lib
Zach Burlingame
5
Bagaimana cara menghubungkan obj dengan ld.exe dari MinGW?
DarrenVortex
4
@DarrenVortexgcc hello.obj
towry
4
Apakah ini juga berfungsi menggunakan linker gratis seperti Alink dari sourceforge.net/projects/alink atau GoLink dari godevtool.com/#linker ? Saya tidak ingin menginstal studio visual hanya untuk itu?
jj_
21

Ini adalah contoh Win32 dan Win64 yang menggunakan panggilan API Windows. Mereka untuk MASM daripada NASM, tapi lihatlah mereka. Anda dapat menemukan detail lebih lanjut di artikel ini .

Ini menggunakan MessageBox daripada mencetak ke 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

Untuk merakit dan menautkan ini menggunakan MASM, gunakan ini untuk 32-bit yang dapat dieksekusi:

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

atau ini untuk 64-bit yang dapat dieksekusi:

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

Mengapa x64 Windows perlu mencadangkan 28 jam byte ruang tumpukan sebelum a call? Itu berarti 32 byte (0x20) shadow space alias ruang rumah, seperti yang disyaratkan oleh konvensi pemanggil. Dan 8 byte untuk re-menyelaraskan stack dengan 16, karena konvensi pemanggilan membutuhkan RSP menjadi 16-byte selaras sebelum sebuah call. ( mainPemanggil kami (dalam kode startup CRT) melakukan itu. Alamat pengembalian 8-byte berarti RSP berjarak 8 byte dari batas 16-byte saat masuk ke suatu fungsi.)

Shadow space dapat digunakan oleh sebuah fungsi untuk membuang argumen registernya di sebelah tempat setiap argumen tumpukan (jika ada). A system callmembutuhkan 30 jam (48 byte) untuk juga menyediakan ruang untuk r10 dan r11 selain 4 register yang disebutkan sebelumnya. Tetapi panggilan DLL hanyalah panggilan fungsi, meskipun panggilan tersebut membungkus sekitar syscallinstruksi.

Fakta menarik: non-Windows, yaitu konvensi pemanggilan Sistem V x86-64 (misalnya di Linux) tidak menggunakan spasi bayangan sama sekali, dan menggunakan hingga 6 argumen register integer / pointer, dan hingga 8 argumen FP di register XMM .


Dengan menggunakan invokedirektif MASM (yang mengetahui konvensi pemanggilan), Anda dapat menggunakan salah satu ifdef untuk membuat versi ini yang dapat dibangun sebagai 32-bit atau 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

Varian makro sama untuk keduanya, tetapi Anda tidak akan belajar perakitan dengan cara ini. Anda akan belajar asm gaya-C sebagai gantinya. invokeadalah untuk stdcallatau fastcallsementara cinvokeuntuk cdeclatau variabel argumen fastcall. Assembler tahu mana yang akan digunakan.

Anda dapat membongkar keluaran untuk melihat seberapa invokeluas.

PhiS
sumber
1
1 untuk jawaban Anda. Bisakah Anda menambahkan kode assembly untuk Windows di ARM (WOA) juga?
Annie
1
Mengapa rsp membutuhkan 0x28 byte dan bukan 0x20? Semua referensi tentang konvensi pemanggilan mengatakan bahwa itu harus 32 tetapi tampaknya membutuhkan 40 dalam praktiknya.
douggard
Dalam kode kotak pesan 32-bit Anda, untuk beberapa alasan ketika saya menggunakan titlesebagai nama label, saya mengalami kesalahan. Namun ketika saya menggunakan sesuatu yang lain sebagai nama label mytitle, semuanya berfungsi dengan baik.
pengguna3405291
bagaimana melakukannya tanpa termasuk?
bluejayke
13

Flat Assembler tidak membutuhkan linker tambahan. Ini membuat pemrograman assembler cukup mudah. Ini juga tersedia untuk Linux.

Ini hello.asmdari contoh 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 membuat eksekusi:

> fasm hello.asm
perakit datar versi 1.70.03 (memori 1048575 kilobyte)
4 lintasan, 1536 byte.

Dan inilah program di IDA :

masukkan deskripsi gambar di sini

Anda dapat melihat tiga panggilan: GetCommandLine, MessageBoxdan ExitProcess.

ceving
sumber
ini menggunakan include dan GUI bagaimana kita melakukannya hanya untuk CMD tanpa menyertakan sama sekali?
bluejayke
Mencoba membaca manual? flatassembler.net/docs.php?article=manual#2.4.2
ceving
dapatkah Anda mengarahkan saya ke bagian yang menulis ke konsol tanpa dll?
bluejayke
12

Untuk mendapatkan .exe dengan NASM'compiler dan linker Visual Studio, kode ini berfungsi dengan baik:

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

Jika kode ini disimpan di misalnya "test64.asm", maka untuk mengkompilasi:

nasm -f win64 test64.asm

Menghasilkan "test64.obj" Lalu untuk menautkan dari command prompt:

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

di mana path_to_link bisa jadi C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin atau di mana pun program link.exe Anda di mesin Anda, path_to_libs bisa berupa C: \ Program Files (x86) \ Windows Kits \ 8.1 \ Lib \ winv6.3 \ um \ x64 atau di mana pun perpustakaan Anda (dalam hal ini kernel32.lib dan user32.lib berada di tempat yang sama, jika tidak gunakan satu opsi untuk setiap jalur yang Anda butuhkan) dan / largeaddressaware: tidak ada opsi yang diperlukan untuk menghindari keluhan linker tentang alamat ke long (untuk user32.lib dalam kasus ini). Selain itu, seperti yang dilakukan di sini, jika Visual's linker dipanggil dari command prompt, maka lingkungan perlu disiapkan sebelumnya (jalankan sekali vcvarsall.bat dan / atau lihat MS C ++ 2010 dan mspdb100.dll ).

rarias
sumber
2
Saya sangat merekomendasikan menggunakan default reldi bagian atas file Anda sehingga mode pengalamatan tersebut ( [msg]dan [title]) menggunakan pengalamatan relatif RIP daripada absolut 32-bit.
Peter Cordes
Terima kasih telah menjelaskan cara menautkan! Anda menyelamatkan kesehatan mental saya. Saya mulai menarik rambut saya karena 'kesalahan LNK2001: simbol eksternal yang belum terselesaikan ExitProcess' dan kesalahan serupa ...
Nik
5

Kecuali jika Anda menelepon suatu fungsi, ini sama sekali tidak sepele. (Dan, sungguh, tidak ada perbedaan nyata dalam kompleksitas antara memanggil printf dan memanggil fungsi api win32.)

Bahkan DOS int 21h benar-benar hanya sebuah pemanggilan fungsi, meskipun itu adalah API yang berbeda.

Jika Anda ingin melakukannya tanpa bantuan, Anda perlu berbicara dengan perangkat keras video Anda secara langsung, kemungkinan besar menulis bitmap dari huruf "Hello world" ke dalam framebuffer. Bahkan kemudian kartu video melakukan pekerjaan menerjemahkan nilai-nilai memori tersebut menjadi sinyal VGA / DVI.

Perhatikan bahwa, sungguh, tidak satu pun dari hal-hal ini sampai ke perangkat keras yang lebih menarik di ASM daripada di C. Program "hello world" bermuara pada pemanggilan fungsi. Satu hal yang menyenangkan tentang ASM adalah Anda dapat menggunakan ABI apa pun yang Anda inginkan dengan cukup mudah; Anda hanya perlu tahu apa itu ABI.

Kapten Segfault
sumber
Ini adalah poin yang sangat baik --- ASM dan C keduanya bergantung pada fungsi yang disediakan OS (_WriteFile di Windows). Jadi dimana keajaibannya? Ini ada dalam kode driver perangkat untuk kartu video.
Assad Ebrahim
2
Ini benar-benar di samping intinya. Poster tersebut menanyakan program assembler yang berjalan "di bawah Windows". Itu berarti fasilitas Windows dapat digunakan (misalnya kernel32.dll), tetapi tidak untuk fasilitas lain seperti libc di bawah Cygwin. Untuk menangis dengan keras, poster itu secara eksplisit mengatakan tidak ada perpustakaan-c.
Albert van der Horst
5

Contoh terbaik adalah mereka yang memiliki fasm, karena fasm tidak menggunakan linker, yang menyembunyikan kompleksitas pemrograman windows oleh lapisan kompleksitas buram lainnya. Jika Anda puas dengan program yang menulis ke jendela gui, maka ada contohnya di direktori contoh fasm.

Jika Anda menginginkan program konsol, yang memungkinkan pengalihan standar masuk dan keluar standar yang juga dimungkinkan. Ada program contoh (helas sangat non-sepele) tersedia yang tidak menggunakan gui, dan bekerja secara ketat dengan konsol, yaitu fasm itu sendiri. Ini dapat dikurangi menjadi hal-hal penting. (Saya telah menulis kompiler keempat yang merupakan contoh non-gui lainnya, tetapi juga tidak sepele).

Program semacam itu memiliki perintah berikut untuk menghasilkan header yang tepat untuk 32-bit yang dapat dieksekusi, biasanya dilakukan oleh linker.

FORMAT PE CONSOLE 

Sebuah bagian bernama '.idata' berisi tabel yang membantu windows selama startup untuk beberapa nama fungsi ke alamat runtime. Ini juga berisi referensi ke KERNEL.DLL yang merupakan Sistem Operasi 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

Format tabel diberlakukan oleh windows dan berisi nama yang dicari di file sistem, saat program dimulai. FASM menyembunyikan beberapa kerumitan di balik kata kunci rva. Jadi _ExitProcess @ 4 adalah label fasm dan _exitProcess adalah string yang dicari oleh Windows.

Program Anda ada di bagian '.text'. Jika Anda menyatakan bahwa bagian itu dapat dibaca dan dapat dieksekusi, itu adalah satu-satunya bagian yang perlu Anda tambahkan.

    section '.text' code executable readable writable

Anda dapat memanggil semua fasilitas yang Anda nyatakan di bagian .idata. Untuk program konsol, Anda memerlukan _GetStdHandle untuk menemukan dia mengajukanescriptors untuk standard in dan standardout (menggunakan nama simbolik seperti STD_INPUT_HANDLE yang ditemukan fasm di file include win32a.inc). Setelah Anda memiliki deskriptor file, Anda dapat melakukan WriteFile dan ReadFile. Semua fungsi dijelaskan dalam dokumentasi kernel32. Anda mungkin menyadarinya atau Anda tidak akan mencoba pemrograman assembler.

Singkatnya: Ada tabel dengan nama asci yang berpasangan dengan OS windows. Selama permulaan ini diubah menjadi tabel alamat yang dapat dipanggil, yang Anda gunakan dalam program Anda.

Albert van der Horst
sumber
FASM mungkin tidak menggunakan linker tetapi masih harus merakit file PE. Yang berarti bahwa itu sebenarnya tidak hanya mengumpulkan kode tetapi juga mengambil sendiri pekerjaan yang biasanya dilakukan oleh penaut, dan karena itu, menurut pendapat saya, menyesatkan untuk menyebut absensi penaut "menyembunyikan kompleksitas", justru sebaliknya - tugas assembler adalah merakit sebuah program, tetapi serahkan pada linker untuk menanamkan program tersebut ke dalam image program yang mungkin bergantung pada banyak hal. Karena itu, menurut saya pemisahan antara linker dan assembler adalah hal yang baik , yang tampaknya, Anda tidak setuju.
amn
@ amn Pikirkan seperti ini. Jika Anda menggunakan linker untuk membuat program di atas, apakah itu memberi Anda lebih banyak wawasan tentang apa program itu, atau terdiri dari apa? Jika saya melihat dari sumber fasm saya mengetahui struktur program secara lengkap.
Albert van der Horst
Poin yang adil. Di sisi lain, memisahkan penautan dari yang lainnya juga memiliki manfaatnya. Anda biasanya memiliki akses ke file objek (yang memungkinkan seseorang untuk memeriksa struktur program juga, terlepas dari format file gambar program), Anda dapat memanggil linker lain yang Anda sukai, dengan opsi yang berbeda. Ini tentang usabilitas dan komposabilitas. Dengan pemikiran tersebut, FASM melakukan segalanya karena "nyaman" melanggar prinsip-prinsip tersebut. Saya pada dasarnya tidak menentangnya - saya melihat pembenaran mereka untuk itu - tetapi saya, untuk satu, tidak membutuhkannya.
amn
mendapatkan kesalahan untuk isntruction ilegal di baris atas di jendela 64 bit
fasm
@bluejayke Mungkin Anda tidak memiliki dokumentasi tentang fasm. FORMAT PE menghasilkan 32 bit yang dapat dieksekusi, yang tidak dapat dijalankan oleh jendela 64 bit. Untuk program 64 bit Anda menginginkan FORMAT PE64. Juga pastikan Anda menggunakan instruksi 64 bit yang tepat dalam program Anda.
Albert van der Horst
3

Jika Anda ingin menggunakan NASM dan Visual Studio linker (link.exe) dengan contoh Hello World anderstornvig, Anda harus menautkan secara manual dengan C Runtime Libary yang berisi fungsi printf ().

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

Semoga ini bisa membantu seseorang.

tboerman.dll
sumber
Poster pertanyaan ingin tahu, bagaimana seseorang akan menulis printf berdasarkan fasilitas yang disediakan Windows, jadi ini sekali lagi benar-benar tidak penting.
Albert van der Horst