Programmare in C senza utilizzare il compilatore ( o quasi )

E’ possibile programmare in C, senza compilare con gcc, sfruttando le dll e nasm; nessun utilizzo di gcc e programmi più snelli.

Di recente, mi sono interessato alle dll di Windows ed ho scoperto che contengono numerose funzioni, scritte in C; in particolare, la dll “msvcrt.dll” ne contiene tantissime ( credo tutte ) della libreria “stdio.h”. Poiché mi piace sperimentare, mi sono chiesto come utilizzare queste funzioni. Dopo varie ricerche su internet e qualche esperimento, ecco cosa ho “scoperto”.

Ottenere un eseguibile exe, che chiama funzioni C senza compilare con gcc.

Per prima cosa ho scritto un semplice programma in C di esempio e l’ho salvato nel file “prg.c”.

Ecco il sorgente:

#include <stdio.h>

int numero;

int main() {

    scanf("%d",&numero);


if (numero < 256) { printf("%c\n",numero);};

    return 0;

            }

IL programma prende un input numerico da tastiera e stampa il carattere col codice ASCII corrispondente. Se, per esempio, si introduce 65, verrà stampato il carattere “A”, perché il codice ASCII 65 corrisponde al carattere “A” maiuscola.

Utilizzando gcc ( unica occasione ), con il comando PROMPT> gcc -S prg.c -masm=intel ho ottenuto un sorgente assembly contenuto nel file “prg.s”. Questo sorgente assembly, non è adatto per l’assemblatore NASM, quindi ho rinominato il file cambiandone l’estensione in .asm e dopo alcune elaborazioni ho ottenuto il file “prg-nasm.asm”, costruito con la sintassi per l’assemblatore NASM.

Infine ho costruito l’eseguibile exe, perfettamente funzionante, con i due seguenti comandi, in sequenza:

PROMPT> nasm -f win64 prg-nasm.asm -o prg-nasm.o

PROMPT> ld msvcrt.dll prg-nasm.o -o prg-nasm-dll.exe

L’eseguibile ottenuto, perfettamente funzionante, occupa meno di 5KB, mentre l’eseguibile che si ottiene compilando con gcc, occupa circa 53KB.

La dll “msvcrt.dll” è presente nel sistema operativo Windows; basta cercarla e copiarla nella propria cartella di lavoro locale. Io ho trovato più di un file con questo nome e ho preso il primo che mi è capitato.

I sorgenti

Per concludere riporto i vari sorgenti, di cui parlo nell’articolo.

File “prg.s”:

	.file	"prg.c"
	.intel_syntax noprefix
	.text
	.comm	numero, 4, 2
	.def	__main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
.LC0:
	.ascii "%d\0"
.LC1:
	.ascii "%c\12\0"
	.text
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
	push	rbp
	.seh_pushreg	rbp
	mov	rbp, rsp
	.seh_setframe	rbp, 0
	sub	rsp, 32
	.seh_stackalloc	32
	.seh_endprologue
	call	__main
	lea	rax, numero[rip]
	mov	rdx, rax
	lea	rcx, .LC0[rip]
	call	scanf
	lea	rax, numero[rip]
	mov	eax, DWORD PTR [rax]
	cmp	eax, 255
	jg	.L2
	lea	rax, numero[rip]
	mov	eax, DWORD PTR [rax]
	mov	edx, eax
	lea	rcx, .LC1[rip]
	call	printf
.L2:
	mov	eax, 0
	add	rsp, 32
	pop	rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
	.def	scanf;	.scl	2;	.type	32;	.endef
	.def	printf;	.scl	2;	.type	32;	.endef

File “prg-nasm.asm”:

bits 64

section	.text

	global	main
        extern  scanf
        extern  printf
	
main:
	push	rbp
	
	mov	rbp, rsp
	
	sub	rsp, 32
	
	
	lea	rax, numero
	mov	rdx, rax
	lea	rcx, LC0
	call	scanf
	lea	rax, numero
	mov	eax, [rax]
	cmp	eax, 255
	jg	L2
	lea	rax, numero
	mov	eax, [rax]
	mov	edx, eax
	lea	rcx, LC1
	call	printf
L2:
	mov	eax, 0
	add	rsp, 32
	pop	rbp
	ret


section .data

LC0: db "%d",0

LC1: db "%c",12,0


section .bss

numero: resb 2


File “prg.c” (già riportato in precedenza):

#include <stdio.h>

int numero;

int main() {

    scanf("%d",&numero);


if (numero < 256) { printf("%c\n",numero);};

    return 0;

            }

Spero che l’articolo vi piaccia e possa esservi utile.

Alla prossima.

Andrea Paolini ( Amministratore del Blog )