5

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.

neatnick
  • 1,489
  • 1
  • 18
  • 29
mcandre
  • 22,868
  • 20
  • 88
  • 147
  • You could use conditional assembly eg. in NASM and that way use eg. `int 80h` for Linux, and for Windows you could use Windows API calls (I don't know about Windows API). For creating really tiny `ELF` executables, [A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux](http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html) might be interesting (but probably won't help much in multiplatform assembly programming). – nrz Sep 20 '12 at 22:02
  • The reason a Hello World program in C typically takes 80K is due to the C runtime library, not the code size. If you don't link in a CRT, your EXE should be 5-10K. – BitBank Sep 20 '12 at 23:43
  • @BitBank, thanks! Could you help me rewrite the code so that it doesn't depend on the C runtime? – mcandre Sep 21 '12 at 04:40
  • @nrz, Teensy is the right idea, but you're right, it's not enough. `ld ... -nostdlib` results in an executable with the exactly same filesize as before. And `ld ... -nostartfiles` results in an executable that shows a weird error when hello.exe is run: `process_begin: CreateProcess(NULL, hello.exe, ...) failed.`. – mcandre Sep 21 '12 at 05:19

5 Answers5

2

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

BitBank
  • 8,500
  • 3
  • 28
  • 46
1

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.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • Could you give an example of code for this that would assemble with some assembly program and produce working executables in both Unix and Windows? – mcandre Sep 20 '12 at 21:11
  • Have a look at http://stackoverflow.com/questions/4568306/outputting-hello-world-in-masm-using-win32-functions and http://stackoverflow.com/questions/3314919/compile-run-assembler-in-linux – Greg Hewgill Sep 20 '12 at 21:13
  • thanks, but those examples are OS-specific. Furthermore, they don't even use the same assembler. The Windows one uses MASM, the Linux ones use Gas. – mcandre Sep 20 '12 at 21:18
  • You can't, by definition, write "cross-platform" assembly code, at least not if you want to interact with the OS itself in any way. As you can see, the examples are clearly very different. – Greg Hewgill Sep 20 '12 at 21:27
  • HLA. LLVM. The very NASM and YASM code I listed at the start of my post are multiplatform. This guy even wrote a GUI application in assembler that runs on both Windows and Linux. http://www.dreamincode.net/forums/topic/292403-nasm-cross-os-app-for-linuxwindows-using-gtk/ – mcandre Sep 20 '12 at 21:29
  • On windows, I get a file size of 2,048 bytes with your code. puts uses CDECL so you should add an add esp, 4 after it. also, you do not need leave since you never set up a stack frame. – Gunner Sep 21 '12 at 02:02
  • @Gunner, What's your toolchain? I'm using Strawberry Perl and MinGW mostly. – mcandre Sep 21 '12 at 04:39
  • @Gunner, and which commands are you using to build executables? Are you using the same Makefile I'm using? Maybe the difference lies there. – mcandre Sep 21 '12 at 05:00
  • @Gunner, when I remove the `leave` instruction, the executable still builds, but when it runs, Windows pops up a "hello.exe has stopped working" error message. – mcandre Sep 21 '12 at 05:05
  • I am guessing you are not adjusting esp after your call to puts. – Gunner Sep 22 '12 at 01:08
  • @Gunner, why do you have to adjust `esp` after calling a C lib function? – mcandre Sep 23 '12 at 18:17
  • @Gunner, you say that you get 2048 bytes when you build this code. What's your development toolchain? Which assemblers, linkers, compilers are you using? – mcandre Sep 23 '12 at 23:09
  • For windows, I use NASM and GoLink for 2048. On Linux, I use NASM and just ld for 2027 bytes. – Gunner Sep 23 '12 at 23:24
0

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.

Jens Björnhager
  • 5,632
  • 3
  • 27
  • 47
  • Thanks, that would cut down on size, but I'd prefer to include all necessary components inside the executable, excepting of course any pieces that would be installed in the OS by default, such as built-in Windows DLLs. – mcandre Sep 21 '12 at 04:39
  • `msvcrt.dll` *is* built into Windows since Windows 2000, I think. – Jens Björnhager Sep 21 '12 at 06:21
  • could you help me rewrite the link command in the makefile to do this dynamically? – mcandre Sep 21 '12 at 16:09
  • Sorry, I'm not familiar with linkers, but what happens if you remove the lib refrences? – Jens Björnhager Sep 21 '12 at 17:15
  • If I remove the `-L...` flag, the program still builds, but the resulting executable generates a "hello.exe has stopped working" popup from Windows when it runs. Removing any of the `-lXYZ` flags results in linker errors due to some C lib dependency or other being missing. – mcandre Sep 22 '12 at 00:31
0

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.

mcandre
  • 22,868
  • 20
  • 88
  • 147
0

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.

Gunner
  • 5,780
  • 2
  • 25
  • 40