mcc

mcc is a machine code compiler.

Log | Files | << Repositories


tree e51c4b1e9ff6e1f200546348f4ddebaa07c693a1
parent fab3a56d9021c1f6a5e45f72a2d5c9bfeff5fbba
author esote <esote.net@gmail.com> 1558146334 -0500
committer esote <esote.net@gmail.com> 1558146334 -0500
gpgsig -----BEGIN PGP SIGNATURE-----
 
 iHUEABYIAB0WIQTXAxYDuIzimYoNSPuhTmRAjzzC8gUCXSArHQAKCRChTmRAjzzC
 8tsBAQD+//h/1r46DUImzZK12cywQlxIcdidxq6FymDKvdAXkwEA7TrrWA2arxYA
 w+Nxm+bncyZoZmdK6gPhxmpPB6hCnQ8=
 =PuRW
 -----END PGP SIGNATURE-----

Produce both 32 and 64-bit executables

Properly read memsize.
Use options structure to keep track of useful values.
Cast to avoid superfluous warnings with debug.

 Makefile         |  16 ++-
 README           |  15 ++-
 hello_i386.mcc   |   2 +-
 hello_x86_64.mcc |  19 ++++
 mcc.c            | 302 ++++++++++++-------------------------------------------
 mcc.h            |  38 +++++++
 write.h          |  24 +++++
 write_32.c       | 172 +++++++++++++++++++++++++++++++
 write_64.c       | 172 +++++++++++++++++++++++++++++++
 9 files changed, 514 insertions(+), 246 deletions(-)

diff --git a/Makefile b/Makefile
index 8ef55fe..f97cf54 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,14 @@
-mcc: mcc.c
-	gcc -O2 -o mcc.out mcc.c
+PROG=	mcc
+SRCS=	mcc.c write_32.c write_64.c
 
-debug: mcc.c
-	gcc -g -Wall -Wextra -Wconversion -o mcc.out mcc.c
+CFLAGS=		-O2 -fstack-protector -D_FORTIFY_SOURCE=2 -pie -fPIE
+LDFLAGS=	-Wl,-z,now -Wl,-z,relro
+
+$(PROG): $(SRCS)
+	gcc $(CFLAGS) $(LDFLAGS) -o $(PROG).out $(SRCS)
+
+debug: $(SRCS)
+	gcc -g -Wall -Wextra -Wconversion -o $(PROG).out $(SRCS)
 
 clean:
-	rm -f mcc.out
+	rm -f $(PROG).out
diff --git a/README b/README
index 9c23cea..53feb23 100644
--- a/README
+++ b/README
@@ -1,10 +1,15 @@
-Reads machine code text and produces i386 ELF executables.
+Reads machine code text and produces x86-64 or i386 Linux-compatible ELF
+executables.
 
 Accepts ASCII text '0' and '1'. All other characters are ignored except the
 comment token ';'. Comments begin with ';' and continue until EOL.
 
-Only uses the .bss and .text sections, starting at 0x804a000 and 0x8049000
-respectively. Page size is set to 0x1000 (4096). .bss is aligned to a 16-byte
-constraint and .text to an 8-byte constraint.
+Programmer's manual:
+	In 64-bit mode .bss and .text start at 0x402000 and 0x401000
+	respectively, and in 32-bit mode 0x804a000 and 0x8049000 respectively.
 
-Currently may only work with the Linux kernel.
+	Program header segment alignment is set to 0x1000 (page size on most
+	systems).
+
+	.bss is aligned to a 16-byte constraint and .text to an 8-byte
+	constraint.
diff --git a/hello_i386.mcc b/hello_i386.mcc
index a52a3cb..3ef0251 100644
--- a/hello_i386.mcc
+++ b/hello_i386.mcc
@@ -1,6 +1,6 @@
 ; MCC
 
-; Compile with ./mcc.out -m 7 hello_i386.mcc
+; Compile with ./mcc.out -3 -m 7 hello_i386.mcc
 
 11000111 00000101 00000000 10100000 00000100 00001000 01001000 00000000 00000000 00000000	; mov DWORD [buffer + 0], 'H'	; buffer is 0x804a000
 11000111 00000101 00000001 10100000 00000100 00001000 01100101 00000000 00000000 00000000	; mov DWORD [buffer + 1], 'e'
diff --git a/hello_x86_64.mcc b/hello_x86_64.mcc
new file mode 100644
index 0000000..d1ac7f8
--- /dev/null
+++ b/hello_x86_64.mcc
@@ -0,0 +1,19 @@
+; MCC
+
+; Compile with ./mcc.out -m 7 hello_x86_64.mcc
+
+11000111 00000100 00100101 00000000 00100000 01000000 00000000 01001000 00000000 00000000 00000000	; mov DWORD [buffer + 0], 'H'	; buffer is 0x402000
+11000111 00000100 00100101 00000001 00100000 01000000 00000000 01100101 00000000 00000000 00000000	; mov DWORD [buffer + 1], 'e'
+11000111 00000100 00100101 00000010 00100000 01000000 00000000 01101100 00000000 00000000 00000000	; mov DWORD [buffer + 2], 'l'
+11000111 00000100 00100101 00000011 00100000 01000000 00000000 01101100 00000000 00000000 00000000	; mov DWORD [buffer + 3], 'l'
+11000111 00000100 00100101 00000100 00100000 01000000 00000000 01101111 00000000 00000000 00000000	; mov DWORD [buffer + 4], 'o'
+11000111 00000100 00100101 00000101 00100000 01000000 00000000 00100001 00000000 00000000 00000000	; mov DWORD [buffer + 5], '!'
+11000111 00000100 00100101 00000110 00100000 01000000 00000000 00001010 00000000 00000000 00000000	; mov DWORD [buffer + 6], 0xA
+01001000 10111000 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000		; mov rax, 1			; sys_write
+01001000 10111111 00000001 00000000 00000000 00000000 00000000 00000000 00000000 00000000		; mov rdi, 1			; stdout
+01001000 10111110 00000000 00100000 01000000 00000000 00000000 00000000 00000000 00000000		; mov rsi, buffer
+01001000 10111010 00000111 00000000 00000000 00000000 00000000 00000000 00000000 00000000		; mov rdx, 0x7			; buffer size
+00001111 00000101											; syscall
+01001000 10111000 00111100 00000000 00000000 00000000 00000000 00000000 00000000 00000000		; mov rax, 60			; sys_exit
+01001000 10111111 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000		; mov rdi, 0			; exit success
+00001111 00000101											; syscall
diff --git a/mcc.c b/mcc.c
index 3945d59..b1883d8 100644
--- a/mcc.c
+++ b/mcc.c
@@ -18,86 +18,72 @@
 
 #include <sys/stat.h>
 
-#include <elf.h>
 #include <err.h>
+#include <errno.h>
 #include <getopt.h>
-#include <stddef.h>
+#include <inttypes.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <string.h>
 
-uint32_t	byte_len(FILE *const);
+#include "mcc.h"
+#include "write.h"
 
-uint32_t	write_elf_hdr(FILE *const, uint32_t const);
-
-void	write_text_hdr(FILE *const, uint32_t const, uint32_t const, int const);
-void	write_bss_hdr(FILE *const, uint32_t const);
-
-void	write_mcode(FILE *const, FILE *const);
-
-void	write_section_names(FILE *const);
-
-void	write_null_section(FILE *const);
-void	write_text_section(FILE *const, uint32_t const, uint32_t const);
-void	write_bss_section(FILE *const, uint32_t const);
-void	write_shstrtab_section(FILE *const, uint32_t const, uint32_t const);
-
-#define BSS_VADDR	0x804a000
-#define TEXT_VADDR	0x8049000
-
-/* length of section header string including null terminator */
-#define NULL_LEN	(0 + 1)
-#define SHSTRTAB_LEN	(9 + 1)
-#define TEXT_LEN	(5 + 1)
-#define BSS_LEN		(4 + 1)
-
-#define SHSTRTAB_ADDRALIGN	(1 << 0)
-#define BSS_ADDRALIGN		(1 << 2)
-#define TEXT_ADDRALIGN		(1 << 4)
-
-/* index in section header string table section */
-#define SHSTRTAB_INDEX	1
-#define TEXT_INDEX	(SHSTRTAB_LEN + 1)
-#define BSS_INDEX	(SHSTRTAB_LEN + TEXT_LEN+1)
-
-#define PAGE_SIZE	4096
+uint64_t	byte_len(FILE *const);
+void		write_mcode(FILE *const, FILE *const);
+void		write_section_names(FILE *const);
 
 #define COMMENT	';'
 
-static unsigned char const zero = 0;
-
 int
 main(int argc, char *argv[])
 {
 	FILE *in;
 	FILE *out;
+	struct mcc_opts opts;
+	char *end;
 	char *iname;
 	char const *oname;
-	uint32_t len;
-	uint32_t mem;
-	uint32_t off;
+	uint64_t mem;
+	int use_64;
 	int ch;
-	int self_m;
+
+	opts.self_m = 0;
 
 	oname = "a.out";
 	mem = 0;
-	self_m = 0;
 
-	while ((ch = getopt(argc, argv, "m:o:s")) != -1) {
+	/* default 64-bit */
+	use_64 = 1;
+	opts.bss_vaddr.n64 = 0x402000;
+	opts.text_vaddr.n64 = 0x401000;
+
+	while ((ch = getopt(argc, argv, "3m:o:s")) != -1) {
 		switch (ch) {
+		case '3':
+			use_64 = 0;
+			opts.bss_vaddr.n32 = 0x804a000;
+			opts.text_vaddr.n32 = 0x8049000;
+			break;
 		case 'm':
 			/* TODO remove BSS section if mem == 0 */
-			mem = atoi(optarg);
+			mem = strtoull(optarg, &end, 10);
+
+			if (errno == EINVAL || errno == ERANGE) {
+				err(1, "memsize invalid");
+			} else if (optarg == end) {
+				errx(1, "no memsize read");
+			}
+
 			break;
 		case 'o':
 			oname = optarg;
 			break;
 		case 's':
-			self_m = 1;
+			opts.self_m = 1;
 			break;
 		default:
 			(void)fprintf(stderr,
-				"usage: %s [-s] [-m memsize] [-o file] "
+				"usage: %s [-3s] [-m memsize] [-o file] "
 				"[file]\n",
 				argv[0]);
 			return 1;
@@ -105,8 +91,10 @@ main(int argc, char *argv[])
 	}
 
 	if (mem == 0) {
-		warnx("mem is 0, using .bss could segfault or overflow into "
-			"other sections");
+		warnx("memsize is 0, using .bss could segfault or overflow "
+			"into other sections");
+	} else if (!use_64 && mem > UINT32_MAX) {
+		warnx("memsize may overflow");
 	}
 
 	argv += optind;
@@ -123,21 +111,34 @@ main(int argc, char *argv[])
 		err(1, "open out");
 	}
 
-	len = byte_len(in);
-
-	off = write_elf_hdr(out, len);
-
-	write_text_hdr(out, off, len, self_m);
-	write_bss_hdr(out, mem);
+	if (use_64) {
+		opts.mem.n64 = mem;
+		opts.len.n64 = byte_len(in);
+		write_ehdr64(out, &opts);
+		write_phdr64_text(out, &opts);
+		write_phdr64_bss(out, &opts);
+	} else {
+		opts.mem.n32 = (uint32_t)mem;
+		opts.len.n32 = (uint32_t)byte_len(in);
+		write_ehdr32(out, &opts);
+		write_phdr32_text(out, &opts);
+		write_phdr32_bss(out, &opts);
+	}
 
 	write_mcode(in, out);
-
 	write_section_names(out);
 
-	write_null_section(out);
-	write_text_section(out, off, len);
-	write_bss_section(out, mem);
-	write_shstrtab_section(out, off, len);
+	if (use_64) {
+		write_shdr64_null(out);
+		write_shdr64_text(out, &opts);
+		write_shdr64_bss(out, &opts);
+		write_shdr64_shstrtab(out, &opts);
+	} else {
+		write_shdr32_null(out);
+		write_shdr32_text(out, &opts);
+		write_shdr32_bss(out, &opts);
+		write_shdr32_shstrtab(out, &opts);
+	}
 
 	if (fclose(in) == EOF) {
 		err(1, "close in");
@@ -154,11 +155,11 @@ main(int argc, char *argv[])
 	return 0;
 }
 
-uint32_t
+uint64_t
 byte_len(FILE *const in)
 {
 	unsigned char buf;
-	uint32_t len;
+	uint64_t len;
 
 	len = 0;
 
@@ -193,103 +194,10 @@ out:
 	return len / 8;
 }
 
-uint32_t
-write_elf_hdr(FILE *const out, uint32_t const len)
-{
-	Elf32_Ehdr e = {0};
-
-	e.e_ident[EI_MAG0] = ELFMAG0;
-	e.e_ident[EI_MAG1] = ELFMAG1;
-	e.e_ident[EI_MAG2] = ELFMAG2;
-	e.e_ident[EI_MAG3] = ELFMAG3;
-	/* TODO support 64-bit */
-	e.e_ident[EI_CLASS] = ELFCLASS32;
-	/* TODO support big endian */
-	e.e_ident[EI_DATA] = ELFDATA2LSB;
-	e.e_ident[EI_VERSION] = EV_CURRENT;
-	/* TODO support other ABIs */
-	e.e_ident[EI_OSABI] = ELFOSABI_SYSV;
-	e.e_ident[EI_ABIVERSION] = 0;
-	/* TODO support writing other object file types */
-	e.e_type = ET_EXEC;
-	/* TODO support other architectures */
-	e.e_machine = EM_386;
-	e.e_version = EV_CURRENT;
-	e.e_flags = 0;
-
-	e.e_ehsize = sizeof(Elf32_Ehdr);
-	e.e_phentsize = sizeof(Elf32_Phdr);
-	e.e_shentsize = sizeof(Elf32_Shdr);
-
-	e.e_phnum = 2;
-	e.e_shnum = 4;
-	e.e_shstrndx = 3;
-
-	/* program hdrs start after elf hdr */
-	e.e_phoff = e.e_ehsize;
-
-	e.e_entry = TEXT_VADDR + e.e_ehsize + e.e_phnum * e.e_phentsize;
-
-	/* section hdrs start after elf hdr, program hdrs, text, and shstrtab */
-	e.e_shoff = e.e_ehsize + e.e_phnum * e.e_phentsize + len +
-		NULL_LEN + SHSTRTAB_LEN + TEXT_LEN + BSS_LEN;
-
-	if (fwrite(&e, sizeof(Elf32_Ehdr), 1, out) != 1) {
-		err(1, "write elf hdr");
-	}
-
-	/* return offset to mcode */
-	return e.e_ehsize + e.e_phnum * e.e_phentsize;
-}
-
-void
-write_text_hdr(FILE *const out, uint32_t const off, uint32_t const len,
-	int const self_m)
-{
-	Elf32_Phdr text = {0};
-
-	text.p_type = PT_LOAD;
-	text.p_offset = 0;
-	text.p_vaddr = TEXT_VADDR;
-	text.p_paddr = text.p_vaddr;
-	text.p_filesz = len + off;
-	text.p_memsz = text.p_filesz;
-	text.p_flags = PF_X | PF_R;
-
-	if (self_m) {
-		text.p_flags |= PF_W;
-	}
-
-	text.p_align = PAGE_SIZE;
-
-	if (fwrite(&text, sizeof(Elf32_Phdr), 1, out) != 1) {
-		err(1, "write text hdr");
-	}
-}
-
-void
-write_bss_hdr(FILE *const out, uint32_t const mem)
-{
-	Elf32_Phdr bss = {0};
-
-	bss.p_type = PT_LOAD;
-	bss.p_offset = 0;
-	bss.p_vaddr = BSS_VADDR;
-	bss.p_paddr = bss.p_vaddr;
-	bss.p_filesz = 0;
-	bss.p_memsz = mem;
-	bss.p_flags = PF_R | PF_W;
-	bss.p_align = PAGE_SIZE;
-
-	if (fwrite(&bss, sizeof(Elf32_Phdr), 1, out) != 1) {
-		err(1, "write bss hdr");
-	}
-}
-
 void
 write_mcode(FILE *const in, FILE *const out)
 {
-	uint32_t i;
+	uint64_t i;
 	unsigned char buf;
 	unsigned char cur;
 
@@ -309,7 +217,7 @@ write_mcode(FILE *const in, FILE *const out)
 			break;
 		case '0':
 		case '1':
-			cur |= (buf - '0') << --i;
+			cur = (unsigned char)(cur | (buf - '0') << --i);
 
 			if (i == 0) {
 				if (fwrite(&cur, 1, 1, out) != 1) {
@@ -330,6 +238,7 @@ write_section_names(FILE *const out)
 	static char const *const shstrtab = ".shstrtab";
 	static char const *const text = ".text";
 	static char const *const bss = ".bss";
+	static unsigned char const zero = 0;
 
 	/* null section name */
 	if (fwrite(&zero, NULL_LEN, 1, out) != 1) {
@@ -348,80 +257,3 @@ write_section_names(FILE *const out)
 		err(1, "write bss name");
 	}
 }
-
-void
-write_null_section(FILE *const out)
-{
-	Elf32_Shdr null = {0};
-
-	null.sh_name = SHT_NULL;
-
-	if (fwrite(&null, sizeof(Elf32_Shdr), 1, out) != 1) {
-		err(1, "write null section");
-	}
-}
-
-void
-write_text_section(FILE *const out, uint32_t const off, uint32_t const len)
-{
-	Elf32_Shdr text = {0};
-
-	text.sh_name = TEXT_INDEX;
-	text.sh_type = SHT_PROGBITS;
-	text.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
-	text.sh_addr = TEXT_VADDR + off;
-	text.sh_offset = off;
-	text.sh_size = len;
-	text.sh_link = 0;
-	text.sh_info = 0;
-	text.sh_addralign = TEXT_ADDRALIGN;
-	text.sh_entsize = 0;
-
-	if (fwrite(&text, sizeof(Elf32_Shdr), 1, out) != 1) {
-		err(1, "write text section");
-	}
-}
-
-void
-write_bss_section(FILE *const out, uint32_t const mem)
-{
-	Elf32_Shdr bss = {0};
-
-	bss.sh_name = BSS_INDEX;
-	bss.sh_type = SHT_NOBITS;
-	bss.sh_flags = SHF_WRITE | SHF_ALLOC;
-	bss.sh_addr = BSS_VADDR;
-	/* TODO magic */
-	bss.sh_offset = 0x1000;
-	bss.sh_size = mem;
-	bss.sh_link = 0;
-	bss.sh_info = 0;
-	bss.sh_addralign = BSS_ADDRALIGN;
-	bss.sh_entsize = 0;
-
-	if (fwrite(&bss, sizeof(Elf32_Shdr), 1, out) != 1) {
-		err(1, "write bss section");
-	}
-}
-
-void
-write_shstrtab_section(FILE *const out, uint32_t const off, uint32_t const len)
-{
-	Elf32_Shdr shstrtab = {0};
-
-	shstrtab.sh_name = SHSTRTAB_INDEX;
-	shstrtab.sh_type = SHT_STRTAB;
-	shstrtab.sh_flags = 0;
-	shstrtab.sh_addr = 0;
-	/* TODO magic */
-	shstrtab.sh_offset = off + len;
-	shstrtab.sh_size = NULL_LEN + SHSTRTAB_LEN + TEXT_LEN + BSS_LEN;
-	shstrtab.sh_link = 0;
-	shstrtab.sh_info = 0;
-	shstrtab.sh_addralign = SHSTRTAB_ADDRALIGN;
-	shstrtab.sh_entsize = 0;
-
-	if (fwrite(&shstrtab, sizeof(Elf32_Shdr), 1, out) != 1) {
-		err(1, "write shstrtab section");
-	}
-}
diff --git a/mcc.h b/mcc.h
new file mode 100644
index 0000000..ec02db8
--- /dev/null
+++ b/mcc.h
@@ -0,0 +1,38 @@
+#ifndef MCC_H
+#define MCC_H
+
+union size {
+	uint32_t	n32;
+	uint64_t	n64;
+};
+
+struct mcc_opts {
+	union size	bss_vaddr;
+	union size	text_vaddr;
+
+	union size	len;
+	union size	off;
+
+	union size	mem;
+
+	int	self_m;
+};
+
+#define PAGE_SIZE	4096
+
+#define SHSTRTAB_ADDRALIGN	(1 << 0)
+#define BSS_ADDRALIGN		(1 << 2)
+#define TEXT_ADDRALIGN		(1 << 4)
+
+/* length of section header string including null terminator */
+#define NULL_LEN	(0 + 1)
+#define SHSTRTAB_LEN	(9 + 1)
+#define TEXT_LEN	(5 + 1)
+#define BSS_LEN		(4 + 1)
+
+/* index in section header string table section */
+#define SHSTRTAB_INDEX	1
+#define TEXT_INDEX	(SHSTRTAB_LEN + 1)
+#define BSS_INDEX	(SHSTRTAB_LEN + TEXT_LEN+1)
+
+#endif /* MCC_H */
diff --git a/write.h b/write.h
new file mode 100644
index 0000000..2f8e720
--- /dev/null
+++ b/write.h
@@ -0,0 +1,24 @@
+#ifndef WRITE_H
+#define WRITE_H
+
+void	write_ehdr64(FILE *const, struct mcc_opts *const);
+
+void	write_phdr64_text(FILE *const, struct mcc_opts const *const);
+void	write_phdr64_bss(FILE *const, struct mcc_opts const *const);
+
+void	write_shdr64_null(FILE *const);
+void	write_shdr64_text(FILE *const, struct mcc_opts const *const);
+void	write_shdr64_bss(FILE *const, struct mcc_opts const *const);
+void	write_shdr64_shstrtab(FILE *const, struct mcc_opts const *const);
+
+void	write_ehdr32(FILE *const, struct mcc_opts *const);
+
+void	write_phdr32_text(FILE *const, struct mcc_opts const *const);
+void	write_phdr32_bss(FILE *const, struct mcc_opts const *const);
+
+void	write_shdr32_null(FILE *const);
+void	write_shdr32_text(FILE *const, struct mcc_opts const *const);
+void	write_shdr32_bss(FILE *const, struct mcc_opts const *const);
+void	write_shdr32_shstrtab(FILE *const, struct mcc_opts const *const);
+
+#endif /* WRITE_H */
diff --git a/write_32.c b/write_32.c
new file mode 100644
index 0000000..412996e
--- /dev/null
+++ b/write_32.c
@@ -0,0 +1,172 @@
+#include <elf.h>
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "mcc.h"
+
+void
+write_ehdr32(FILE *const out, struct mcc_opts *const opts)
+{
+	Elf32_Ehdr e = {0};
+
+	e.e_ident[EI_MAG0] = ELFMAG0;
+	e.e_ident[EI_MAG1] = ELFMAG1;
+	e.e_ident[EI_MAG2] = ELFMAG2;
+	e.e_ident[EI_MAG3] = ELFMAG3;
+	e.e_ident[EI_CLASS] = ELFCLASS32;
+	/* TODO support big endian */
+	e.e_ident[EI_DATA] = ELFDATA2LSB;
+	e.e_ident[EI_VERSION] = EV_CURRENT;
+	/* TODO support other ABIs */
+	e.e_ident[EI_OSABI] = ELFOSABI_SYSV;
+	e.e_ident[EI_ABIVERSION] = 0;
+	/* TODO support writing other object file types */
+	e.e_type = ET_EXEC;
+	/* TODO support other architectures */
+	e.e_machine = EM_386;
+	e.e_version = EV_CURRENT;
+	e.e_flags = 0;
+
+	e.e_ehsize = sizeof(Elf32_Ehdr);
+	e.e_phentsize = sizeof(Elf32_Phdr);
+	e.e_shentsize = sizeof(Elf32_Shdr);
+
+	e.e_phnum = 2;
+	e.e_shnum = 4;
+	e.e_shstrndx = 3;
+
+	/* program hdrs start after elf hdr */
+	e.e_phoff = e.e_ehsize;
+
+	opts->off.n32 = (uint32_t)(e.e_ehsize + e.e_phnum * e.e_phentsize);
+
+	e.e_entry = opts->text_vaddr.n32 + opts->off.n32;
+
+	/* section hdrs start after elf hdr, program hdrs, text, and shstrtab */
+	e.e_shoff = opts->off.n32 + opts->len.n32 + NULL_LEN + SHSTRTAB_LEN +
+		TEXT_LEN + BSS_LEN;
+
+	if (fwrite(&e, sizeof(Elf32_Ehdr), 1, out) != 1) {
+		err(1, "write ehdr32");
+	}
+}
+
+void
+write_phdr32_text(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf32_Phdr text = {0};
+
+	text.p_type = PT_LOAD;
+	text.p_offset = 0;
+	text.p_vaddr = opts->text_vaddr.n32;
+	text.p_paddr = text.p_vaddr;
+	text.p_filesz = opts->len.n32 + opts->off.n32;
+	text.p_memsz = text.p_filesz;
+	text.p_flags = PF_X | PF_R;
+
+	if (opts->self_m) {
+		text.p_flags |= PF_W;
+	}
+
+	text.p_align = PAGE_SIZE;
+
+	if (fwrite(&text, sizeof(Elf32_Phdr), 1, out) != 1) {
+		err(1, "write phdr32 text");
+	}
+}
+
+void
+write_phdr32_bss(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf32_Phdr bss = {0};
+
+	bss.p_type = PT_LOAD;
+	bss.p_offset = 0;
+	bss.p_vaddr = opts->bss_vaddr.n32;
+	bss.p_paddr = bss.p_vaddr;
+	bss.p_filesz = 0;
+	bss.p_memsz = opts->mem.n32;
+	bss.p_flags = PF_R | PF_W;
+	bss.p_align = PAGE_SIZE;
+
+	if (fwrite(&bss, sizeof(Elf32_Phdr), 1, out) != 1) {
+		err(1, "write phdr32 bss");
+	}
+}
+
+void
+write_shdr32_null(FILE *const out)
+{
+	Elf32_Shdr null = {0};
+
+	null.sh_name = SHT_NULL;
+
+	if (fwrite(&null, sizeof(Elf32_Shdr), 1, out) != 1) {
+		err(1, "write shdr32 null");
+	}
+}
+
+void
+write_shdr32_text(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf32_Shdr text = {0};
+
+	text.sh_name = TEXT_INDEX;
+	text.sh_type = SHT_PROGBITS;
+	text.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
+	text.sh_addr = opts->text_vaddr.n32 + opts->off.n32;
+	text.sh_offset = opts->off.n32;
+	text.sh_size = opts->len.n32;
+	text.sh_link = 0;
+	text.sh_info = 0;
+	text.sh_addralign = TEXT_ADDRALIGN;
+	text.sh_entsize = 0;
+
+	if (fwrite(&text, sizeof(Elf32_Shdr), 1, out) != 1) {
+		err(1, "write shdr32 text");
+	}
+}
+
+void
+write_shdr32_bss(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf32_Shdr bss = {0};
+
+	bss.sh_name = BSS_INDEX;
+	bss.sh_type = SHT_NOBITS;
+	bss.sh_flags = SHF_WRITE | SHF_ALLOC;
+	bss.sh_addr = opts->bss_vaddr.n32;
+	/* TODO magic */
+	bss.sh_offset = 0x1000;
+	bss.sh_size = opts->mem.n32;
+	bss.sh_link = 0;
+	bss.sh_info = 0;
+	bss.sh_addralign = BSS_ADDRALIGN;
+	bss.sh_entsize = 0;
+
+	if (fwrite(&bss, sizeof(Elf32_Shdr), 1, out) != 1) {
+		err(1, "write shdr32 bss");
+	}
+}
+
+void
+write_shdr32_shstrtab(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf32_Shdr shstrtab = {0};
+
+	shstrtab.sh_name = SHSTRTAB_INDEX;
+	shstrtab.sh_type = SHT_STRTAB;
+	shstrtab.sh_flags = 0;
+	shstrtab.sh_addr = 0;
+	shstrtab.sh_offset = opts->off.n32 + opts->len.n32;
+	shstrtab.sh_size = NULL_LEN + SHSTRTAB_LEN + TEXT_LEN + BSS_LEN;
+	shstrtab.sh_link = 0;
+	shstrtab.sh_info = 0;
+	shstrtab.sh_addralign = SHSTRTAB_ADDRALIGN;
+	shstrtab.sh_entsize = 0;
+
+	if (fwrite(&shstrtab, sizeof(Elf32_Shdr), 1, out) != 1) {
+		err(1, "write shdr32 shstrtab");
+	}
+}
diff --git a/write_64.c b/write_64.c
new file mode 100644
index 0000000..88a6157
--- /dev/null
+++ b/write_64.c
@@ -0,0 +1,172 @@
+#include <elf.h>
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "mcc.h"
+
+void
+write_ehdr64(FILE *const out, struct mcc_opts *const opts)
+{
+	Elf64_Ehdr e = {0};
+
+	e.e_ident[EI_MAG0] = ELFMAG0;
+	e.e_ident[EI_MAG1] = ELFMAG1;
+	e.e_ident[EI_MAG2] = ELFMAG2;
+	e.e_ident[EI_MAG3] = ELFMAG3;
+	e.e_ident[EI_CLASS] = ELFCLASS64;
+	/* TODO support big endian */
+	e.e_ident[EI_DATA] = ELFDATA2LSB;
+	e.e_ident[EI_VERSION] = EV_CURRENT;
+	/* TODO support other ABIs */
+	e.e_ident[EI_OSABI] = ELFOSABI_SYSV;
+	e.e_ident[EI_ABIVERSION] = 0;
+	/* TODO support writing other object file types */
+	e.e_type = ET_EXEC;
+	/* TODO support other architectures */
+	e.e_machine = EM_X86_64;
+	e.e_version = EV_CURRENT;
+	e.e_flags = 0;
+
+	e.e_ehsize = sizeof(Elf64_Ehdr);
+	e.e_phentsize = sizeof(Elf64_Phdr);
+	e.e_shentsize = sizeof(Elf64_Shdr);
+
+	e.e_phnum = 2;
+	e.e_shnum = 4;
+	e.e_shstrndx = 3;
+
+	/* program hdrs start after elf hdr */
+	e.e_phoff = e.e_ehsize;
+
+	opts->off.n64 = (uint64_t)(e.e_ehsize + e.e_phnum * e.e_phentsize);
+
+	e.e_entry = opts->text_vaddr.n64 + opts->off.n64;
+
+	/* section hdrs start after elf hdr, program hdrs, text, and shstrtab */
+	e.e_shoff = opts->off.n64 + opts->len.n64 + NULL_LEN + SHSTRTAB_LEN +
+		TEXT_LEN + BSS_LEN;
+
+	if (fwrite(&e, sizeof(Elf64_Ehdr), 1, out) != 1) {
+		err(1, "write ehdr64");
+	}
+}
+
+void
+write_phdr64_text(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf64_Phdr text = {0};
+
+	text.p_type = PT_LOAD;
+	text.p_offset = 0;
+	text.p_vaddr = opts->text_vaddr.n64;
+	text.p_paddr = text.p_vaddr;
+	text.p_filesz = opts->len.n64 + opts->off.n64;
+	text.p_memsz = text.p_filesz;
+	text.p_flags = PF_X | PF_R;
+
+	if (opts->self_m) {
+		text.p_flags |= PF_W;
+	}
+
+	text.p_align = PAGE_SIZE;
+
+	if (fwrite(&text, sizeof(Elf64_Phdr), 1, out) != 1) {
+		err(1, "write phdr64 text");
+	}
+}
+
+void
+write_phdr64_bss(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf64_Phdr bss = {0};
+
+	bss.p_type = PT_LOAD;
+	bss.p_offset = 0;
+	bss.p_vaddr = opts->bss_vaddr.n64;
+	bss.p_paddr = bss.p_vaddr;
+	bss.p_filesz = 0;
+	bss.p_memsz = opts->mem.n64;
+	bss.p_flags = PF_R | PF_W;
+	bss.p_align = PAGE_SIZE;
+
+	if (fwrite(&bss, sizeof(Elf64_Phdr), 1, out) != 1) {
+		err(1, "write phdr64 bss");
+	}
+}
+
+void
+write_shdr64_null(FILE *const out)
+{
+	Elf64_Shdr null = {0};
+
+	null.sh_name = SHT_NULL;
+
+	if (fwrite(&null, sizeof(Elf64_Shdr), 1, out) != 1) {
+		err(1, "write shdr64 null");
+	}
+}
+
+void
+write_shdr64_text(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf64_Shdr text = {0};
+
+	text.sh_name = TEXT_INDEX;
+	text.sh_type = SHT_PROGBITS;
+	text.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
+	text.sh_addr = opts->text_vaddr.n64 + opts->off.n64;
+	text.sh_offset = opts->off.n64;
+	text.sh_size = opts->len.n64;
+	text.sh_link = 0;
+	text.sh_info = 0;
+	text.sh_addralign = TEXT_ADDRALIGN;
+	text.sh_entsize = 0;
+
+	if (fwrite(&text, sizeof(Elf64_Shdr), 1, out) != 1) {
+		err(1, "write shdr64 text");
+	}
+}
+
+void
+write_shdr64_bss(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf64_Shdr bss = {0};
+
+	bss.sh_name = BSS_INDEX;
+	bss.sh_type = SHT_NOBITS;
+	bss.sh_flags = SHF_WRITE | SHF_ALLOC;
+	bss.sh_addr = opts->bss_vaddr.n64;
+	/* TODO magic */
+	bss.sh_offset = 0x1000;
+	bss.sh_size = opts->mem.n64;
+	bss.sh_link = 0;
+	bss.sh_info = 0;
+	bss.sh_addralign = BSS_ADDRALIGN;
+	bss.sh_entsize = 0;
+
+	if (fwrite(&bss, sizeof(Elf64_Shdr), 1, out) != 1) {
+		err(1, "write shdr64 bss");
+	}
+}
+
+void
+write_shdr64_shstrtab(FILE *const out, struct mcc_opts const *const opts)
+{
+	Elf64_Shdr shstrtab = {0};
+
+	shstrtab.sh_name = SHSTRTAB_INDEX;
+	shstrtab.sh_type = SHT_STRTAB;
+	shstrtab.sh_flags = 0;
+	shstrtab.sh_addr = 0;
+	shstrtab.sh_offset = opts->off.n64 + opts->len.n64;
+	shstrtab.sh_size = NULL_LEN + SHSTRTAB_LEN + TEXT_LEN + BSS_LEN;
+	shstrtab.sh_link = 0;
+	shstrtab.sh_info = 0;
+	shstrtab.sh_addralign = SHSTRTAB_ADDRALIGN;
+	shstrtab.sh_entsize = 0;
+
+	if (fwrite(&shstrtab, sizeof(Elf64_Shdr), 1, out) != 1) {
+		err(1, "write shdr64 shstrtab");
+	}
+}