ABSTRACT

Possiamo buttare completamente PWB! Ecco come scrivere una Applicazione completamente Assembler dentro MSVC con tutta la ricchezza dell’IDE. Presentiamo anche l’"anatomia" di un file Obj e qualche tool di "dissezione". (sconsigliato per i deboli di stomaco di derivazione Java/VB)

INTRO

Abbiamo già visto come sia possibile integrare facilmente codice assembler a 32 bit, scrivere file sorgente esterni, con file "C" e "C++" all’interno di progetto MSVC 6.0 e anche con Ide successivi quando il main file era comunque C. Vediamo se è possibile scrivere completamente in Assembler.

BREVE RADIOGRAFIA DI UN ESEGUIBILE

Riassumiamo in breve come si genera un "exe".

Compilatore

Ogni file sorgente viene passato al compilatore che ne legge il contenuto e genera il binario corrispondente. L’IDE di MSVC chiama il suo compilatore interno, che è un compilatore C++, oppure chiama altri compliatori, se si sono personalizzati i settaggi. (vedi articolo precedente). Oltre a generare il puro codice binario nel rispettivo file OBJ, produce una particolare struttura dati descrittiva, contenente i nomi delle funzioni e variabili dichiarati nel file e disponibili per altri moduli (diremo: "simboli esportati"), ma anche contenente i simboli "richiesti" dall’obj per poter funzionare: diciamo i simboli "importati". Tale struttura viene comunemente detta "symbol table".

Symbol Table: un esempio

Se compiliamo il file sum.asm già citato nel precedente articolo,


    ; SUM.ASM
    .386			; set di istruzioni da utilizzare; anche .586
    .MODEL FLAT, C  ; SOLO FLAT per win32; C per modello param sullo stack
            anche se qui li estraiamo "a mano"
    .CODE   ; inizio codice
    ; .STACK
    Sum PROC
    MOV EDX, ESP
    MOV EAX, [EDX+4]
    MOV EBX, [EDX+8]
    ADD EAX,EBX
    RET
    Sum ENDP; fine proc

Possiamo facilmente vedere il suo contenuto tramite il tool, invocabile solo da linea di comando, dumpbin. Se digitiamo:

dumpbin /all E:\lavori_NT\MIXED1\SUM.obj

Otterremo:

        COFF SYMBOL TABLE
        000 00000000 DEBUG notype Filename | .file
        .\SUM.ASM
        002 00000000 SECT1 notype Static | .text
        Section length B, #relocs 0, #linenums 5, checksum 0
        004 00000000 SECT2 notype Static | .data
        Section length 0, #relocs 0, #linenums 0, checksum 0
        006 00000000 SECT1 notype () External | _Sum
        tag index 00000008 size 0000000B lines 0000006F next function 00000000
        008 00000000 SECT1 notype BeginFunction | .bf
        line# 000b end 00000000
        00A 00000005 SECT1 notype .bf or.ef | .lf
        00B 0000000B SECT1 notype EndFunction | .ef
        line# 0012

(solo parte del lungo dump). Si noti il nome della f. esportata.

Analogamente nel file obj del main:

017 00000000 UNDEF notype () External | _Subtract

018 00000000 UNDEF notype () External | _Sum

dove appunto vengono richieste le due f. Subtract e Sum.

Linker

Il Linker riceve in input TUTTI i file OBJ del progetto, più le Librerie ( per inciso, le librerie NON SONO stdio.h, stdlib.h come dicono alcuni ….) in formato *.lib e tentano di risolvere tutte gli export con tutti gli import. La regola è:

  • ogni export può essere chiamato da una o più import
  • per ogni import uno ed un solo export
  • se ci sono due simboli esportati che hanno lo stesso nome, il linker segnalerà errore: DUPLICATE SYMBOL

Alla fine, con fatica, il linker sputa fuori un EXE, ma non è finita..

Supporto Run-Time ed entry point

Oltre ad unire tutti gli obj ed a risolvere nomi chiamate, il linker aggiunge anche alcune informazioni che serviranno al s.o. per caricare e lanciare il ns programma.

Il lancio di un programma richiede, fra le altre innumerevoli cose, un entry point, un punto di ingresso, o per dirla più dura e giusta, un indirizzo a cui fare CALL o JUMP per cominciare il programma.

Tale punto deve, ovviamente, essere univoco e viene creato a partire da una label generata automaticamente dal compilatore C, o specificata a mano nel sorgente ASM.

Tale label passa quindi nell’obj e poi nell’exe.

IL PROGETTO CON SOLO FILE ASM

Partiamo al solito da un progetto, ma stavolta vuoto, di tipo Console Application:

Creiamo un nuovo file, p.es. hello.asm:

occhio: estensione .ASM !

E copiamoci dentro p.es. l’esempio per NT fornito col MASM:

     .386
    .MODEL flat, stdcall
    STD_OUTPUT_HANDLE EQU -11
    GetStdHandle PROTO NEAR32 stdcall,
    nStdHandle:DWORD
    WriteFile PROTO NEAR32 stdcall,
    hFile:DWORD, lpBuffer:NEAR32, nNumberOfBytesToWrite:DWORD,
    lpNumberOfBytesWritten:NEAR32, lpOverlapped:NEAR32
    ExitProcess PROTO NEAR32 stdcall,
    dwExitCode:DWORD
    .STACK 4096
    .DATA
    msg DB "Hello, world.", 13, 10
    written DD 0
    hStdOut DD 0
    .CODE
    _start:
    INVOKE GetStdHandle,
    STD_OUTPUT_HANDLE ; Standard output handle
    mov hStdOut, eax
    INVOKE WriteFile,
    hStdOut, ; File handle for screen
    NEAR32 PTR msg, ; Address of string
    LENGTHOF msg, ; Length of string
    NEAR32 PTR written, ; Bytes written
    0 ; Overlapped mode
    INVOKE ExitProcess,
    0 ; Result code for parent process
    PUBLIC _start
    END

NOTA: potremmo anche importare il file .MAK, ma vogliamo vedere bene come impostare i settaggi.

Ora parte la fase dei settaggi delle opzioni, non solo di compilazione, ma anche di linkaggio.

Come nel precedente articolo, personalizziamo i "Settings" (click col pulsante destro):

Custom Build:

 nell’edit box "Commands" vanno messi tutti quei comandi da command line che producono l’OBJ invocando l’Assemblatore:

c:\masm611\bin\ml /c /Cx /coff /Zd $(inputpath)

dove appunto c:\masm611\bin\ml è il masm ml.exe con qualche opzione:

/c = Assembla SENZA linker (usiamo il linker di Visual Studio)

/coff = Common Object file Format: che tipo di OBJ generare

/Zd = Aggiungi nomi per debugging a livello sorgente

$(inputpath) è una variabile dell’ambiente ed ha lo stesso nome del file

Nell’edit box va specificato il nome del file di uscita, e la cosa più comoda è:

$(InputName).obj

in modo che abbia lo stesso nome del file sorgente ma con estensione OBJ.

OSS:

se non ci sono particolari esigenze di PATH e/o NOMI, usare

$(inputpath) e $(InputName).obj

Si dovrebbe avere:

La Compilazione

Col classico F5 lanciamo la compilazione, e compila, ma il linker dà errori:

     ------------------Configuration: only_ASM - Win32 Debug-----------------
    Performing Custom Build Step on .\hello.ASM
    Microsoft (R) Macro Assembler Version 6.11
    Copyright (C) Microsoft Corp 1981-1993. All rights reserved.
    Assembling: .\hello.ASM
    Linking...
    LINK : error LNK2001: unresolved external symbol _mainCRTStartup
    Debug/only_ASM.exe : fatal error LNK1120: 1 unresolved externals
    Error executing link.exe.
    only_ASM.exe - 2 error(s), 0 warning(s)
    

Errore di linker, manca un simbolo, quello usualmente settato per default che punta al codice di inizializzazione standard delle applicazione a console.

Personalizziamo. Nella sezione linker rimuoviamo tutte le Lib e DLL non utilizzate, ci basta kernel32.lib:

ricompiliamo, ma non basta ancora, non era quello il motivo, manca l’entry point. Alla sezione LINK, category "Output" aggiungiamo l’entry point specificato nel file asm, _start.

 

Compila! E se poniamo un breakpoint nel sorgente, il debugging funziona:

CONCLUSIONI

E’ possibile utilizzare un sistema 32 bit, a finestre, ben collaudato, per scrivere programmi anche solo costituiti da Assembler PURO.

Con qualche trucco ci siamo sbarazzati per sempre del PWB!

Parte prima