Windows altında assembler'da merhaba dünya nasıl yazılır?


94

Windows altında montajda temel bir şey yazmak istedim, NASM kullanıyorum ama hiçbir şey çalışmıyor.

Windows'ta C işlevlerinin yardımı olmadan merhaba dünya nasıl yazılır ve derlenir?


3
Ayrıca Steve Gibson'ın Small Is Beautiful Windows montaj başlangıç ​​kitini inceleyin.
Jeremy

C-kitaplıklarını kullanmamak biraz garip bir kısıtlamadır. MS-Windows işletim sistemi içindeki bazı kitaplıkları aramak gerekir. Muhtemelen kernel32.dll. Microsoft'un bunu c veya Pascal ile yazıp yazmadığı ilgisiz görünüyor. Yalnızca işletim sistemi tarafından sağlanan işlevlerin çağrılabileceği anlamına mı geliyor, Unix tipi bir sistemde sistem çağrıları olarak adlandırılan şey nedir?
Albert van der Horst

C kitaplıkları ile, GCC veya MSVC ile gelenler gibi bir C çalışma zamanı kitaplıkları kullanmadan kastettiğini varsayıyorum. Elbette kernel32.dll gibi bazı standart Windows DLL'leri kullanmak zorunda kalacak.
Rudy Velthuis

2
Kernel32.dll ile gcc çalışma zamanı kitaplığı arasındaki fark, formatta değil (her ikisi de dll'dir) ve dilde değildir (her ikisi de muhtemelen c'dir, ancak bu gizlidir.) Fark, işletim sistemi tarafından sağlanıp sağlanmamaktadır.
Albert van der Horst

Bunu arıyordum da lol dahil olmadan
fasm

Yanıtlar:


39

NASM örnekleri .

Libc stdio çağrılıyor printf, uygulamaint 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

O zaman koş

nasm -fwin32 helloworld.asm
gcc helloworld.obj
a

Ayrıca , C kütüphanesi kullanmadan Nasm'da The Clueless Newbies Guide to Hello World var . O zaman kod şöyle görünecektir.

MS-DOS sistem çağrılarıyla 16 bit kod: DOS öykünücülerinde veya NTVDM desteğiyle 32 bit Windows'ta çalışır . Herhangi bir 64 bit Windows altında "doğrudan" (şeffaf bir şekilde) çalıştırılamaz, çünkü bir x86-64 çekirdeği vm86 modunu kullanamaz.

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

Bunu bir .comyürütülebilir dosya olarak oluşturun, böylece cs:100htüm segment kayıtları birbirine eşit olacak şekilde yüklenecektir (küçük bellek modeli).

İyi şanslar.


28
Soru açıkça "C kitaplıklarını kullanmadan" dan bahsediyor
Mehrdad Afshari

25
Yanlış. C kitaplığının kendisi açıkça yapabilir, bu yüzden mümkün. Aslında sadece biraz daha zordur. Sadece WriteConsole () 'yi doğru 5 parametre ile çağırmanız gerekir.
MSalters

12
İkinci örnek herhangi bir C kütüphanesi işlevini çağırmasa da, bir Windows programı da değildir. Sanal DOS Makinesi çalıştırmak için ateşlenecek.
Rômulo Ceccon

7
@Alex Hart, ikinci örneği Windows için değil DOS içindir. DOS'ta, küçük moddaki programlar (.COM dosyaları, 64Kb toplam kod + veri + yığın altında) 0x100h'de başlar çünkü segmentteki ilk 256 bayt PSP (komut satırı bağımsız değişkenleri vb.) Tarafından alınır. Bu bağlantıya bakın: en.wikipedia.org/wiki/Program_Segment_Prefix
zvolkov

7
İstenen bu değildi. İlk örnek C kitaplığını kullanır ve ikincisi Windows değil MS-DOS'tur.
Paulo Pinto

131

Bu örnek, doğrudan Windows API'ye nasıl gidileceğini ve C Standard Library'ye nasıl bağlanılmayacağını gösterir.

    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:

Derlemek için NASM ve LINK.EXE'ye ihtiyacınız olacak (Visual studio Standard Edition'dan)

   nasm -fwin32 merhaba.asm
   link / subsystem: console / nodefaultlib / entry: main hello.obj 

21
bunu bağlamak için muhtemelen kernel32.lib'i eklemeniz gerekir (ben yaptım). link / subsystem: console / nodefaultlib / entry: main hello.obj kernel32.lib
Zach Burlingame

5
Objeyi MinGW'den ld.exe'ye nasıl bağlayabilirim?
DarrenVortex

4
@DarrenVortexgcc hello.obj
towry

4
Bu da gelen alink gibi ücretsiz bağlayıcılar kullanılarak İşe yarar sourceforge.net/projects/alink veya golink godevtool.com/#linker ? Sadece bunun için görsel stüdyo kurmak istemiyor muyum?
jj_

21

Bunlar, Windows API çağrılarını kullanan Win32 ve Win64 örnekleridir. NASM'den çok MASM içindir, ama onlara bir bakın. Bu makalede daha fazla ayrıntı bulabilirsiniz .

Bu, standart çıktıya yazdırmak yerine MessageBox kullanır.

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

Bunları MASM kullanarak birleştirmek ve bağlamak için bunu 32 bit yürütülebilir dosya için kullanın:

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

veya 64 bit çalıştırılabilir dosya için bu:

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

X64 Windows'un neden a'dan önce 28 saatlik yığın alanı ayırması gerekiyor call? Bu, arama kuralı gereği 32 baytlık (0x20) gölge alanı, yani ev alanıdır. Çağrı kuralı RSP hizalanmış 16 baytlık olması gerektirdiğinden ve başka bir 8 byte 16 ile yığın yeniden hizalamak önce bir call. ( mainArayanımız (CRT başlangıç ​​kodunda) bunu yaptı. 8 baytlık dönüş adresi, RSP'nin bir işleve girişte 16 baytlık sınırdan 8 bayt uzakta olduğu anlamına gelir.)

Gölge alanı , herhangi bir yığın değişkeninin (varsa) olacağı yerin yanına kendi yazmaç değiştirgelerini dökmek için bir işlev tarafından kullanılabilir. A system call, daha önce bahsedilen 4 yazmaçlara ek olarak r10 ve r11 için de yer ayırmak için 30 saat (48 bayt) gerektirir. Ancak DLL çağrıları, syscalltalimatların etrafına sarılmış olsalar bile, yalnızca işlev çağrılarıdır .

Eğlenceli gerçek: Windows dışı, yani x86-64 System V çağrı kuralı (örneğin Linux'ta) gölge alanı kullanmaz ve 6 adede kadar tamsayı / işaretçi kayıt argümanı ve XMM kayıtlarında 8 adede kadar FP argümanı kullanır .


MASM en Kullanılması invoke(çağırma kuralını bilir) yönergesi, 32-bit veya 64-bit olarak inşa edilebileceği bu bir sürümünü yapmak için bir ifdef kullanabilirsiniz.

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

Makro varyant her ikisi için de aynıdır, ancak montajı bu şekilde öğrenemezsiniz. Bunun yerine C-style asm öğreneceksiniz. invokefor stdcallveya fastcallwhile cinvokeis for cdeclveya değişken bağımsız değişken fastcall. Montajcı hangisini kullanacağını bilir.

Ne kadar invokegenişlediğini görmek için çıktıyı parçalarına ayırabilirsiniz .


1
Cevabınız için +1. Windows için ARM (WOA) montaj kodunu da ekleyebilir misiniz?
Annie

1
Rsp neden 0x20 değil de 0x28 bayt gerektirir? Çağıran sözleşmedeki tüm referanslar, 32 olması gerektiğini söylüyor, ancak pratikte 40 gerektiriyor gibi görünüyor.
douggard

32 bitlik mesaj kutusu kodunuzda, herhangi bir nedenle titleetiket adı olarak kullandığımda hatalarla karşılaşıyorum. Ancak etiket adı gibi başka bir şey kullandığımda mytitle, her şey yolunda gidiyor .
user3405291

dahil olmadan nasıl yapılır?
bluejayke

13

Flat Assembler'ın ekstra bir bağlayıcıya ihtiyacı yoktur. Bu, assembler programlamasını oldukça kolaylaştırır. Linux için de mevcuttur.

Bu, hello.asmFasm örneklerinden:

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 bir yürütülebilir dosya oluşturur:

> fasm merhaba.asm
düz montajcı sürüm 1.70.03 (1048575 kilobayt bellek)
4 geçiş, 1536 bayt.

Ve bu IDA'daki program :

görüntü açıklamasını buraya girin

Üç aramaları görebilirsiniz: GetCommandLine, MessageBoxve ExitProcess.


bu bir include ve GUI kullanıyor
bluejayke


dll olmadan konsola yazan bir bölümü bana gösterebilir misiniz?
bluejayke

12

NASM derleyicisine ve Visual Studio'nun bağlayıcısına sahip bir .exe almak için bu kod iyi çalışır:

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

Bu kod örneğin "test64.asm" üzerine kaydedilmişse, derlemek için:

nasm -f win64 test64.asm

"Test64.obj" üretir Daha sonra komut isteminden bağlanmak için:

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

nerede path_to_link olabilir C: \ Program Files (x86) \ Microsoft Visual Studio 10.0 \ VC \ bin ya da her nerede makinenizin içerisine LINK.EXE program olduğunu path_to_libs olabilir C: \ Program Files (x86) \ Windows Setleri \ 8.1 \ Lib \ winv6.3 \ um \ x64 veya kitaplıklarınız nerede olursa olsun (bu durumda hem kernel32.lib hem de user32.lib aynı yerdedir, aksi takdirde ihtiyacınız olan her yol için bir seçenek kullanın) ve / largeaddressaware: hiçbir seçenek yoktur linker'in uzun adresler hakkında şikayet etmesini önlemek için gereklidir (bu durumda user32.lib için) Ayrıca, burada yapıldığı gibi, Visual'ın bağlayıcı komut isteminden çağrılırsa, ortamı önceden kurmak gerekir (vcvarsall.bat ve / veya MS C ++ 2010 ve mspdb100.dll'ye bakın)).


2
default relDosyanızın en üstünde kullanmanızı şiddetle tavsiye ederim , böylece bu adresleme modları ( [msg]ve [title]) 32-bit mutlak yerine RIP'ye göre adresleme kullanır.
Peter Cordes

Nasıl bağlanacağınızı açıkladığınız için teşekkür ederiz! Ruh sağlığımı kurtardın. 'LNK2001 hatası: çözülmemiş harici sembol ExitProcess' ve benzer hatalar yüzünden saçımı çekmeye başladım ...
Nik

5

Bir fonksiyon çağırmadıkça , bu hiç de önemsiz değil. (Cidden, printf'i çağırmakla bir win32 api işlevini çağırmak arasında gerçek bir karmaşıklık farkı yok.)

DOS int 21h bile, farklı bir API olsa bile, gerçekten sadece bir işlev çağrısıdır.

Yardım almadan yapmak istiyorsanız, video donanımınızla doğrudan konuşmanız gerekir, muhtemelen "Merhaba dünya" harflerinin bit eşlemlerini bir çerçeve arabelleğine yazmanız gerekir. O zaman bile video kartı bu bellek değerlerini VGA / DVI sinyallerine çevirme işini yapıyor.

Aslına bakılırsa, donanıma kadar bu şeylerin hiçbirinin ASM'de C'deki kadar ilginç olmadığını unutmayın. Bir "merhaba dünya" programı bir işlev çağrısı olarak özetlenebilir. ASM ile ilgili güzel bir şey, istediğiniz herhangi bir ABI'yi oldukça kolay kullanabilmenizdir; sadece ABI'nin ne olduğunu bilmeniz gerekiyor.


Bu mükemmel bir nokta --- ASM ve C'nin her ikisi de işletim sistemi tarafından sağlanan bir işleve (Windows'ta _WriteFile) güveniyor. Peki sihir nerede? Video kartının aygıt sürücüsü kodundadır.
Assad Ebrahim

2
Bu tamamen konunun yanı sıra. Poster "Windows altında" çalışan bir assembler programı soruyor. Bu, Windows olanaklarının (örn. Kernel32.dll) kullanılabileceği, ancak Cygwin altındaki libc gibi diğer tesislerin kullanılamayacağı anlamına gelir. Yüksek sesle ağlamak için poster açıkça k-kitaplık olmadığını söylüyor.
Albert van der Horst

5

En iyi örnekler fasm olanlardır, çünkü fasm, başka bir opak karmaşıklık katmanı tarafından Windows programlamasının karmaşıklığını gizleyen bir bağlayıcı kullanmaz. Bir GUI penceresine yazan bir programdan memnunsanız, fasm'ın örnek dizininde bunun bir örneği vardır.

Standart giriş ve standart çıkışların yeniden yönlendirilmesine izin veren bir konsol programı istiyorsanız, bu da mümkündür. Bir GUI kullanmayan ve kesinlikle konsolla çalışan, yani fasm'ın kendisiyle çalışan (helas oldukça önemsiz olmayan) bir örnek program vardır. Bu, esaslara göre inceltilebilir. (Başka bir gui olmayan örnek olan ancak aynı zamanda önemsiz olmayan bir dördüncü derleyici yazdım).

Böyle bir program, normal olarak bir bağlayıcı tarafından yapılan 32-bit yürütülebilir dosya için uygun bir başlık oluşturmak için aşağıdaki komuta sahiptir.

FORMAT PE CONSOLE 

'.İdata' adlı bir bölüm, başlangıç ​​sırasında pencerelerin işlev adlarını çalışma zamanı adresleriyle eşleştirmesine yardımcı olan bir tablo içerir. Ayrıca, Windows İşletim Sistemi olan KERNEL.DLL için bir başvuru içerir.

 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

Tablo formatı, pencereler tarafından empoze edilir ve program başlatıldığında sistem dosyalarında aranan isimleri içerir. FASM, rva anahtar kelimesinin arkasındaki karmaşıklığın bir kısmını gizler. Yani _ExitProcess @ 4 bir fasm etiketi ve _exitProcess Windows tarafından aranan bir dizedir.

Programınız '.text' bölümünde. Bu bölümün okunabilir ve çalıştırılabilir olduğunu bildirirseniz, eklemeniz gereken tek bölüm budur.

    section '.text' code executable readable writable

.İdata bölümünde beyan ettiğiniz tüm tesisleri arayabilirsiniz. Bir konsol programı için, standart giriş ve standart çıkış için dosya tanımlayıcıları bulmak üzere _GetStdHandle'a ihtiyacınız vardır (fasm'ın win32a.inc içerme dosyasında bulduğu STD_INPUT_HANDLE gibi sembolik isimler kullanarak). Dosya tanımlayıcılara sahip olduğunuzda, WriteFile ve ReadFile'ı yapabilirsiniz. Tüm işlevler kernel32 belgelerinde açıklanmıştır. Muhtemelen bunun farkındasınız veya assembler programlamayı denemeyeceksiniz.

Özetle: Windows işletim sistemi ile eşleşen asci adlarına sahip bir tablo var. Başlatma sırasında bu, programınızda kullandığınız çağrılabilir adresler tablosuna dönüştürülür.


FASM bağlayıcı kullanmayabilir, ancak yine de bir PE dosyası oluşturması gerekir. Bu, aslında sadece kodu bir araya getirmekle kalmayıp, aynı zamanda normalde bir bağlayıcının yapacağı bir işi de üstlendiği anlamına gelir ve bu nedenle, benim mütevazi görüşüme göre, bir bağlayıcının yokluğunu "gizleyen karmaşıklık" olarak adlandırmak yanıltıcıdır, tam tersine - derleyicinin görevi, bir programı bir araya getirmektir, ancak programı, birçok şeye bağlı olabilecek bir program görüntüsüne yerleştirmek için bağlayıcıya bırakmaktır. Bu nedenle, bir bağlayıcı ile birleştirici arasındaki ayrımı iyi bir şey buluyorum , öyle görünüyor ki, buna katılmıyorsunuz.
amn

@amn Bu şekilde düşünün. Yukarıdaki programı oluşturmak için bir bağlayıcı kullanırsanız, programın ne yaptığı veya nelerden oluştuğu konusunda size daha fazla fikir verir mi? Eğer fasm kaynağına bakarsam, programın bütün yapısını biliyorum.
Albert van der Horst

Doğru tespit. Kapak tarafında, bağlantıyı diğer her şeyden ayırmanın da faydaları var. Normalde bir nesne dosyasına erişiminiz vardır (bu, programın görüntü dosyası formatından bağımsız olarak bir programın yapısını da incelemesine uzun bir yol kat eder), farklı seçeneklerle tercihinize göre farklı bir bağlayıcıyı çağırabilirsiniz. Yeniden kullanılabilirlik ve birleştirilebilirlik ile ilgili. Bunu akılda tutarak, FASM her şeyi "uygun" olduğu için yapıyor, bu ilkeleri çiğniyor. Ben temelde buna karşı değilim - bunun gerekçesini görüyorum - ama ben buna ihtiyacım yok.
amn

fasm 64 bit pencerelerde üst satırda yasadışı isntruction için hata al
bluejayke

@bluejayke Muhtemelen elinizde fasm belgelerine sahip değildiniz. FORMAT PE, 64 bitlik bir pencerenin çalışmayı reddettiği 32 bitlik bir yürütülebilir dosya oluşturur. 64 bitlik bir program için FORMAT PE64'ü istiyorsunuz. Ayrıca programınızda uygun 64 bit talimatları kullandığınızdan emin olun.
Albert van Horst der

3

NASM ve Visual Studio'nun bağlayıcısını (link.exe) anderstornvig'in Hello World örneğiyle kullanmak istiyorsanız, printf () işlevini içeren C Runtime Libary ile manuel olarak bağlantı kurmanız gerekecektir.

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

Umarım bu birine yardımcı olur.


Soruların posteri, Windows'un sağladığı olanaklara göre birinin nasıl printf yazacağını bilmek istiyor, bu yüzden bu yine tamamen amacın dışında.
Albert van Horst der
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.