Live data from Hacker News

The smallest Hello World program

blog.lohr.dev

41–50 of 53 posts

Re: The smallest Hello World program

#43
post #27

here's an 80 byte x86_64 linux 'hello world' (okay, not 'Hello world!'). convert to binary with xxd -r -p: 7f454c46488d3537000000ffc7b20eeb03003e00 b001eb1a01000000050000001800000000000000 1800000005000000b03c0f05ebfa380001006865 6c6c0000010068656c6c00006f20776f726c640a i'm sure this can be improved -- but i could never get any x86_64 linux elf to under 80 bytes. see if you can fit the exclamation point still.

Yeah I thought sth like this is possible, but (correct me if I'm wrong) this (ab)uses the ELF header and punts data in there, which goes against my requirement

> It should be a ‘proper‘ executable binary according to the spec

Re: The smallest Hello World program

#45
post #21
post #2

These challenges are funny - they remind me of the old days. Back in the DOS/Windows days, we used to have the .com format, which was perfect for tiny programs. One could even write a program of less than 10 bytes that could actually do something! We've come a long way since then, and is like, at some point, nobody cared about optimizing executable size anymore

debug -a 100 178A:0100 int 19 178A:0102 -r cx CX 0000 :2 -n reboot.com -w Writing 00002 bytes -q

Some more:

Quick'n'dirty:

    .model small
    .code
     org 100h
    start:
     int 19h                 ; Bootstrap loader
    end start
More "correct":

    .model small
    .code
     org 100h
    start:
     db 0EAh                 ; Jump to Power On Self Test - Cold Boot
     dw 0,0FFFFh
    end start
Even more "correct":

    .model small
    .code
     org 100h
    start:
     mov ah,0Dh
     int 21h                 ; DOS Services  ah=function 0Dh
                             ;  flush disk buffers to disk
     sti                     ; Enable interrupts
     hlt                     ; Halt processor
     mov al,0FEh
     out 64h,al              ; port 64h, kybd cntrlr functn
                             ;  al = 0FEh, pulse CPU reset
    end start

Re: The smallest Hello World program

#46
post #43
post #27

here's an 80 byte x86_64 linux 'hello world' (okay, not 'Hello world!'). convert to binary with xxd -r -p: 7f454c46488d3537000000ffc7b20eeb03003e00 b001eb1a01000000050000001800000000000000 1800000005000000b03c0f05ebfa380001006865 6c6c0000010068656c6c00006f20776f726c640a i'm sure this can be improved -- but i could never get any x86_64 linux elf to under 80 bytes. see if you can fit the exclamation point still.

Yeah I thought sth like this is possible, but (correct me if I'm wrong) this (ab)uses the ELF header and punts data in there, which goes against my requirement > It should be a ‘proper‘ executable binary according to the spec

yes, this one conforms to 'whatever linux agrees to exec(2)', which apparently is a lot that is out of spec.

Re: The smallest Hello World program

#47
post #39
post #7

This is pretty bad. Let's start with the very first instruction: mov rax, 1 An actual "mov rax, 1" would assemble to 48 B8 01 00 00 00 00 00 00 00, a whopping TEN bytes. nasm will optimize this to the equivalent "mov eax, 1", that's 6 bytes, but still: xor eax, eax ; 2 bytes inc eax ; 2 bytes would be much smaller. Second line: mov rdi, 1 You already have the value 1 in eax, so a "mov edi, eax" (two bytes) would suff…

I was able to shave off one additional byte with this: ... xor rax, rax ; = 0 inc rax ; = 1 - syscall: sys_write mov rdi, rax ; copy 1 - file descriptor: stdout lea rsi, [rel msg] ; pointer to message mov rdx, 14 ; message length syscall ... $ nasm -f bin -o elf elf.asm; wc -c elf; ./elf 166 elf Hello, World! So I guess NASM already optimizes this quite well However, using the stack-based instructions as xpasky hinte…

That second snippet is pretty funny:

  push 1
  pop rax
  pop rdi
You can't push a value once and pop it twice, that's not how a stack works! You're popping something else off the stack. So why does this even work?

Linux passes your program arguments on the stack, with argc on top. So when you don't pass any arguments, argc just HAPPENS to be 1. Which you then pop into rdi. Gross!

Re: The smallest Hello World program

#48
post #47
post #39

Earlier quoted context omitted.

I was able to shave off one additional byte with this: ... xor rax, rax ; = 0 inc rax ; = 1 - syscall: sys_write mov rdi, rax ; copy 1 - file descriptor: stdout lea rsi, [rel msg] ; pointer to message mov rdx, 14 ; message length syscall ... $ nasm -f bin -o elf elf.asm; wc -c elf; ./elf 166 elf Hello, World! So I guess NASM already optimizes this quite well However, using the stack-based instructions as xpasky hinte…

That second snippet is pretty funny: push 1 pop rax pop rdi You can't push a value once and pop it twice, that's not how a stack works! You're popping something else off the stack. So why does this even work? Linux passes your program arguments on the stack, with argc on top. So when you don't pass any arguments, argc just HAPPENS to be 1. Which you then pop into rdi. Gross!

Of course - you are completely right, an oversight in wanting to correct my mistake as quickly as possible.

With that fixed, is there any reason not to use push here?

Re: The smallest Hello World program

#49
post #39
post #7

This is pretty bad. Let's start with the very first instruction: mov rax, 1 An actual "mov rax, 1" would assemble to 48 B8 01 00 00 00 00 00 00 00, a whopping TEN bytes. nasm will optimize this to the equivalent "mov eax, 1", that's 6 bytes, but still: xor eax, eax ; 2 bytes inc eax ; 2 bytes would be much smaller. Second line: mov rdi, 1 You already have the value 1 in eax, so a "mov edi, eax" (two bytes) would suff…

I was able to shave off one additional byte with this: ... xor rax, rax ; = 0 inc rax ; = 1 - syscall: sys_write mov rdi, rax ; copy 1 - file descriptor: stdout lea rsi, [rel msg] ; pointer to message mov rdx, 14 ; message length syscall ... $ nasm -f bin -o elf elf.asm; wc -c elf; ./elf 166 elf Hello, World! So I guess NASM already optimizes this quite well However, using the stack-based instructions as xpasky hinte…

should be ... push 1 ; syscall: sys_write pop rax push 1 pop rdi

of course

Re: The smallest Hello World program

#50
post #48
post #47

Earlier quoted context omitted.

That second snippet is pretty funny: push 1 pop rax pop rdi You can't push a value once and pop it twice, that's not how a stack works! You're popping something else off the stack. So why does this even work? Linux passes your program arguments on the stack, with argc on top. So when you don't pass any arguments, argc just HAPPENS to be 1. Which you then pop into rdi. Gross!

Of course - you are completely right, an oversight in wanting to correct my mistake as quickly as possible. With that fixed, is there any reason not to use push here?

Yes, because:

  push 1       ; 6A 01 (2 bytes)
  pop rdi      ; 5F    (1 byte)
is longer than a simple:

  mov edi, eax ; 89 C7 (2 bytes)
Post reply on HN