I wrote an example program using a custom made ELF-32 header using data declaration statements. I wrote many comments which reference the official specification. This was an exercise both in programming and also Technical Reading and Writing. I had to read enough of the specification PDF file to understand what I was doing. I then tried to write descriptive comments that at the very least I would understand when I need to remind myself how this format is created.
FASM Assembler 32-bit ELF Hello World
;Chastity's Source for ELF 32-bit executable creation
;
;All data as defined in this file is based off of the specification of the ELF file format.
;I first looked at the type of file created by FASM's "format ELF executable" directive.
;It is great that FASM can create an executable file automatically. (Thanks Tomasz Grysztar, you are a true warrior!)
;However, I wanted to understand the format for theoretical use in other assemblers like NASM.
;However, the syntax of FASM and NASM is still different enough that this will work only in FASM.
;I do have a NASM version in a separate file in my repository though.
;The Github repository with the spec I used is here.
;<https://github.com/xinuos/gabi>
;And this is the wikipedia article which linked me to the specification document
;<https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>
;This file contains a raw binary ELF32 header created using db,dw,dd commands.
;After that, it proceeds to assemble a real "Hello World!" program
;Header for 32 bit ELF executable (with comments based on specification)
db 0x7F,"ELF" ;ELFMAGIC: 4 bytes that identify this as an ELF file. The magic numbers you could say.
db 1 ;EI_CLASS: 1=32-bit 2=64-bit
db 1 ;EI_DATA: The endianness of the data. 1=ELFDATA2LSB 2=ELFDATA2MSB For Intel x86 this is always 1 as far as I know.
db 1 ;EI_VERSION: 1=EV_CURRENT (ELF identity version 1) (which is current at time of specification Version 4.2 I was using)
db 9 dup 0 ;padding zeros to bring us to address 0x10
dw 2 ;e_type: 2=ET_EXEC (executable instead of object file)
dw 3 ;e_machine : 3=EM_386 (Intel 80386) 0x3E (AMD x86-64 architecture)
dd 1 ;e_version: 1=EV_CURRENT (ELF object file version.)
p_vaddr = 0x8048000 ;the absolute base address where the file is loaded into memory
e_entry = 0x8048054 ;program starts running at this address (right after header)
dd e_entry ;e_entry: the address at which the program starts running
dd 0x34 ;e_phoff: where in the file the program header offset is
db 8 dup 0 ;e_shoff and e_flags are unused in this example,therefore all zeros
dw 0x34 ;e_ehsize: size of the ELF header
dw 0x20 ;e_phentsize: size of program header which happens after ELF header
dw 1 ;e_phnum: How many program headers. Only 1 in this case
dw 0x28 ;e_shentsize: Size of a section header
dw 0 ;e_shnum number of section headers
dw 0 ;e_shstrndx: section header string index (not used here)
;That is the end of the 0x34 byte (52 bytes decimal) ELF header. Sadly, this is not the end and a program header is also required (what drunk person made this format?)
dd 1 ;p_type: 1=PT_LOAD
dd 0 ;p_offset: Base address from file (zero)
dd p_vaddr ;p_vaddr: Virtual address in memory where the file will be.
dd p_vaddr ;p_paddr: Physical address. Same as previous
;The file_size variable I have defined uses some trickery to get the size of the file.
;An EOF constant (End Of File) is defined at the end of the program code
;By subtracting the program virtual address from that address,
;I get the actual number of bytes of this entire program
file_size equ EOF-p_vaddr ;Place the actual size of the file using NASM address constants
dd file_size ;p_filesz: Size of file image of the segment. Must be equal to the file size or greater
dd file_size ;p_memsz: Size of memory image of the segment, which may be equal to or greater than file image.
dd 7 ;p_flags: permission flags: 7=4(Read)+2(Write)+1(Execute)
dd 0x1000 ;p_align; Alignment (same page alignment that FASM uses of 4096 bytes)
;important FASM directives
use32 ;tell assembler that 32 bit code is being used
org e_entry ;origin of new code begins here
;Now, the actual hello world program
mov eax,4 ;invoke SYS_WRITE (kernel opcode 4 on 32 bit systems)
mov ebx,1 ;write to the STDOUT file
mov ecx,msg ;pointer/address of string to write
mov edx,13 ;number of bytes to write
int 80h
mov eax,1 ;function SYS_EXIT (kernel opcode 1 on 32 bit systems)
mov ebx,0 ;return 0 status on exit - 'No Errors'
int 80h
msg db 'Hello World!',0Ah,0
EOF = $ ; define a label for the end of file. This is used in the ELF header
;To Assemble and run this program on Linux, you can use the following makefile.
;You can also disassemble it with ndisasm because the exact address of code is known
;main-fasm:
; fasm ELF-32-hello.asm
; chmod +x ELF-32-hello.bin
; ./ELF-32-hello.bin
;ndisasm:
; ndisasm -b 32 -o 0x8048054 -e 0x54 ELF-32-hello.bin
NASM Assembler 32-bit ELF Hello World
;Chastity's Source for ELF 32-bit executable creation
;
;All data as defined in this file is based off of the specification of the ELF file format.
;I first looked at the type of file created by FASM's "format ELF executable" directive.
;It is great that FASM can create an executable file automatically. (Thanks Tomasz Grysztar, you are a true warrior!)
;However, I wanted to understand the format for theoretical use in other assemblers like NASM.
;Therefore, what you see here is a complete Hello World program that should work within NASM
;to create an executable file without using a linker. It worked perfectly on my machine running Debian Linux and NASM version 2.16.01.
;The Github repository with the spec I used is here.
;<https://github.com/xinuos/gabi>
;And this is the wikipedia article which linked me to the specification document
;<https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>
;This file contains a raw binary ELF32 header created using db,dw,dd commands.
;After that, it proceeds to assemble a real "Hello World!" program
;Header for 32 bit ELF executable (with comments based on specification)
db 0x7F,"ELF" ;ELFMAGIC: 4 bytes that identify this as an ELF file. The magic numbers you could say.
db 1 ;EI_CLASS: 1=32-bit 2=64-bit
db 1 ;EI_DATA: The endianness of the data. 1=ELFDATA2LSB 2=ELFDATA2MSB For Intel x86 this is always 1 as far as I know.
db 1 ;EI_VERSION: 1=EV_CURRENT (ELF identity version 1) (which is current at time of specification Version 4.2 I was using)
db 9 dup 0 ;padding zeros to bring us to address 0x10
dw 2 ;e_type: 2=ET_EXEC (executable instead of object file)
dw 3 ;e_machine : 3=EM_386 (Intel 80386) 0x3E (AMD x86-64 architecture)
dd 1 ;e_version: 1=EV_CURRENT (ELF object file version.)
p_vaddr equ 0x8048000 ;the absolute base address where the file is loaded into memory
e_entry equ 0x8048054 ;program starts running at this address (right after header)
dd e_entry ;e_entry: the address at which the program starts running
dd 0x34 ;e_phoff: where in the file the program header offset is
db 8 dup 0 ;e_shoff and e_flags are unused in this example,therefore all zeros
dw 0x34 ;e_ehsize: size of the ELF header
dw 0x20 ;e_phentsize: size of program header which happens after ELF header
dw 1 ;e_phnum: How many program headers. Only 1 in this case
dw 0x28 ;e_shentsize: Size of a section header
dw 0 ;e_shnum number of section headers
dw 0 ;e_shstrndx: section header string index (not used here)
;That is the end of the 0x34 byte (52 bytes decimal) ELF header. Sadly, this is not the end and a program header is also required (what drunk person made this format?)
dd 1 ;p_type: 1=PT_LOAD
dd 0 ;p_offset: Base address from file (zero)
dd p_vaddr ;p_vaddr: Virtual address in memory where the file will be.
dd p_vaddr ;p_paddr: Physical address. Same as previous
;The file_size variable I have defined uses some trickery to get the size of the file.
;An EOF constant (End Of File) is defined at the end of the program code
;By subtracting the program virtual address from that address,
;I get the actual number of bytes of this entire program
file_size equ EOF-p_vaddr ;Place the actual size of the file using NASM address constants
dd file_size ;p_filesz: Size of file image of the segment. Must be equal to the file size or greater
dd file_size ;p_memsz: Size of memory image of the segment, which may be equal to or greater than file image.
dd 7 ;p_flags: permission flags: 7=4(Read)+2(Write)+1(Execute)
dd 0x1000 ;p_align; Alignment (same page alignment that FASM uses of 4096 bytes)
;important NASM directives
use32 ;tell assembler that 32 bit code is being used
org p_vaddr ;origin of new code begins here
;Now, the actual hello world program
mov eax,4 ;invoke SYS_WRITE (kernel opcode 4 on 32 bit systems)
mov ebx,1 ;write to the STDOUT file
mov ecx,msg ;pointer/address of string to write
mov edx,13 ;number of bytes to write
int 80h
mov eax,1 ;function SYS_EXIT (kernel opcode 1 on 32 bit systems)
mov ebx,0 ;return 0 status on exit - 'No Errors'
int 80h
msg db 'Hello World!',0Ah,0
EOF equ $ ; define a label for the end of file. This is used in the ELF header
;To Assemble and run this program on Linux, you can use the following makefile.
;You can also disassemble it with ndisasm because the exact address of code is known
;main-nasm:
; nasm ELF-32-hello.asm
; chmod +x ELF-32-hello
; ./ELF-32-hello
;ndisasm:
; ndisasm -b 32 -o 0x8048054 -e 0x54 ELF-32-hello
I made a repository for examples like this. Others may want to understand the header used on Linux systems.
Leave a comment