mcandre
mcandre

Reputation: 24662

How do I cut out assembler executable bloat?

I've got working multiplatform Hello World code in Gas, NASM, and YASM, and I would like to shrink their corresponding executable files from 76KB to something more reasonable for a Hello World assembly program, seeing as a basic Hello World C program leads to an 80KB executable, and assembly should be much smaller. I believe the bulk of the executables are filled with junk from the linker options.

Trace:

LIBS=c:/strawberry/c/i686-w64-mingw32/lib/crt2.o -Lc:/strawberry/c/i686-w64-mingw32/lib -lmingw32 -lmingwex -lmsvcrt

ld ld -o $(EXECUTABLE) hello.o $(LIBS)

hello.exe
Hello World!

Code:

.data

msg: .ascii "Hello World!\0"

.text

.global _main

_main:

pushl $msg
call _puts

leave
movl $0, %eax
ret

If I remove any of the options in LIBS, either the link process fails, or the resulting executable raises a Windows error when it runs. So the logical thing to do is replace the puts call with something simpler, like sys_write, but I don't know how to do this multiplatform. The little documentation online says to use int 0x80 to perform a call to the kernel, but this only works in Linux, not in Windows, and I want my assembly code to be multiplatform.

Upvotes: 5

Views: 528

Answers (5)

Gunner
Gunner

Reputation: 5884

Because almost all C functions use the CDECL calling convention where you the caller adjusts the stack not the callee (the function).

You will get into trouble if you don't learn how to do things correctly now, read harder to trackdown bugs.

Try this:

    push    szLF
    push    esp
    push    fmtint2
    call    printf
    add     esp, 4 * 3

    push msg
    call puts 

    push    szLF
    push    esp
    push    fmtint2
    call    printf
    add     esp, 4 * 3

Run it and notice the numbers before and after your call to puts. They are different no? Well, they are supposed to be the same. Now add:

    add     esp, 4

after your call to puts and run it again.. The numbers are the same now? That means you have a balanced stack pointer and the function uses the CDECL calling convention.

Upvotes: 0

mcandre
mcandre

Reputation: 24662

The assembler bloat is most likely coming from the C lib dependencies, especially for puts. refactoring the code to print Hello World without using a C call will most likely require OS-specific assembly code, as the Unix standard involves interrupts that make calls to the kernel, and Windows has its own VB-like API for such tasks.

I did manage to find a solution that would create small executable while still maintaining platform agnosticism. Ordinarily, C preprocessor directives would do the trick, but I'm not sure which assembly languages even have preprocessor syntax. But a similar effect can be achieved through the use of controlled, included assembly code files. A collection of wrapper code files can handle OS-specific assembly code, while an included assembly file does the rest. And a simple Makefile can run the respective build console commands to reference the respective wrapper code on the desired platform.

For example, I was able to quickly construct FASM code that works this way. (Though I have yet to inform it to actually bypass puts with something less bloaty.) Anyway, it's progress.

Upvotes: 0

BitBank
BitBank

Reputation: 8725

Your program bloat comes mostly from the C runtime library. In Windows, a simple hello world program can be < 5K if you write your own "tiny" CRT. Here is a link to a project which explains all of the details about how to shrink your EXE to its smallest possible size:

http://www.codeproject.com/Articles/15156/Tiny-C-Runtime-Library

Upvotes: 2

Jens Bj&#246;rnhager
Jens Bj&#246;rnhager

Reputation: 5648

You should be able to link dynamically to the C runtime library instead of including it statically. I don't know how to do it in Linux, but in Windows you can use msvcrt.dll.

Upvotes: 0

Greg Hewgill
Greg Hewgill

Reputation: 994391

For Windows, you can call the native Win32 API functions, such as GetStdHandle() and WriteFile() to write directly to stdout.

For Unix-like systems, you can call the write() syscall with file descriptor 1 for stdout.

The details of exactly how you do each of these will depend on which assembler and OS you are using.

Upvotes: 1

Related Questions