From 89bab9883341fcf1a3e0e7770b5431710e2c2e4c Mon Sep 17 00:00:00 2001 From: pancake Date: Fri, 26 Nov 2021 18:03:15 +0100 Subject: [PATCH] native: implement a working hello world compilation for w64 (#12577) --- cmd/tools/vtest-all.v | 17 ++++ vlib/v/gen/native/amd64.v | 102 ++++++++++++++++++++++-- vlib/v/gen/native/gen.v | 12 +++ vlib/v/gen/native/macho.v | 9 ++- vlib/v/gen/native/pe.v | 159 +++++++++++++++++++++++++------------- 5 files changed, 236 insertions(+), 63 deletions(-) diff --git a/cmd/tools/vtest-all.v b/cmd/tools/vtest-all.v index aaf74da119..76ca6ac4fd 100644 --- a/cmd/tools/vtest-all.v +++ b/cmd/tools/vtest-all.v @@ -71,6 +71,23 @@ fn get_all_commands() []Command { line: '$vexe run examples/v_script.vsh > /dev/null' okmsg: 'V can run the .VSH script file examples/v_script.vsh' } + // + res << Command{ + line: '$vexe -os linux -b native -o hw.linux examples/hello_world.v' + okmsg: 'V compiles hello_world.v on the native backend for linux' + rmfile: 'hw.linux' + } + res << Command{ + line: '$vexe -os macos -b native -o hw.macos examples/hello_world.v' + okmsg: 'V compiles hello_world.v on the native backend for macos' + rmfile: 'hw.macos' + } + res << Command{ + line: '$vexe -os windows -b native -o hw.exe examples/hello_world.v' + okmsg: 'V compiles hello_world.v on the native backend for windows' + rmfile: 'hw.exe' + } + // res << Command{ line: '$vexe -b js -o hw.js examples/hello_world.v' okmsg: 'V compiles hello_world.v on the JS backend' diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index a4a32c35ef..cc27eb1bbc 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -204,6 +204,14 @@ fn (mut g Gen) mov64(reg Register, val i64) { g.write8(0xc7) g.write8(0xc1) } + .rdx { + g.write8(0x48) + g.write8(0xc7) + g.write8(0xc2) + g.write32(i32(int(val))) + g.println('mov32 $reg, $val') + return + } .rbx { g.write8(0x48) g.write8(0xc7) @@ -427,7 +435,8 @@ pub fn (mut g Gen) allocate_string(s string, opsize int) int { g.strings << s str_pos := g.buf.len + opsize g.str_pos << str_pos - return 0 + g.strs << String{s, str_pos} + return str_pos } pub fn (mut g Gen) cld_repne_scasb() { @@ -484,7 +493,45 @@ pub fn (mut g Gen) gen_print_reg(r Register, n int, fd int) { g.syscall() } +pub fn (mut g Gen) apicall(s string) { + if g.pref.os != .windows { + g.n_error('apicalls are only for windows') + } + g.write8(0xff) + g.write8(0x15) + delta := match s { + 'WriteFile' { + -(0xbcc + g.buf.len) + } + 'GetStdHandle' { + -(0xbcc + g.buf.len + 8) + } + 'ExitProcess' { + -(0xbcc + g.buf.len + 16) + } + else { + 0 + } + } + g.write32(delta) +} + pub fn (mut g Gen) gen_print(s string, fd int) { + if g.pref.os == .windows { + g.sub(.rsp, 0x38) + g.mov(.rcx, -11) + g.apicall('GetStdHandle') + g.mov_reg(.rcx, .rax) + // g.mov64(.rdx, g.allocate_string(s, 3)) + g.lea(.rdx, g.allocate_string(s, 3)) + g.mov(.r8, s.len) // string length + g.write([byte(0x4c), 0x8d, 0x4c, 0x24, 0x20]) // lea r9, [rsp+0x20] + g.write([byte(0x48), 0xc7, 0x44, 0x24, 0x20]) + g.write32(0) // mov qword[rsp+0x20], 0 + // g.mov(.r9, rsp+0x20) + g.apicall('WriteFile') + return + } // // qq := s + '\n' // @@ -554,8 +601,21 @@ pub fn (mut g Gen) gen_amd64_exit(expr ast.Expr) { g.n_error('native builtin exit expects a numeric argument') } } - g.mov(.eax, g.nsyscall_exit()) - g.syscall() + if g.pref.os == .windows { + g.mov_reg(.rcx, .rdi) + g.apicall('ExitProcess') + } else { + g.mov(.eax, g.nsyscall_exit()) + g.syscall() + } + g.trap() // should never be reached, just in case +} + +fn (mut g Gen) lea(reg Register, val int) { + g.write8(0x48) + g.write8(0x8d) + g.write8(0x15) + g.write32(val) } fn (mut g Gen) mov(reg Register, val int) { @@ -569,10 +629,15 @@ fn (mut g Gen) mov(reg Register, val int) { return } .rcx { - g.write8(0x48) - g.write8(0xc7) - g.write8(0xc1) - g.write32(-1) + if val == -1 { + g.write8(0x48) + g.write8(0xc7) + g.write8(0xc1) + g.write32(-1) + } else { + g.write8(0xff) + g.write8(0xff) // mov rcx 0xffff5 + } return } else { @@ -629,7 +694,16 @@ fn (mut g Gen) mov(reg Register, val int) { g.write8(0xbf) } .rcx { + g.write8(0x48) g.write8(0xc7) + g.write8(0xc1) + } + .r8 { + g.write8(0x41) + g.write8(0xb8) + } + .r9 { + g.write8(0xb9) } .rdx, .edx { g.write8(0xba) @@ -738,6 +812,14 @@ fn (mut g Gen) mov_reg(a Register, b Register) { g.write8(0x48) g.write8(0x89) g.write8(0xf8) + } else if a == .rcx && b == .rdi { + g.write8(0x48) + g.write8(0x89) + g.write8(0xf9) + } else if a == .rcx && b == .rax { + g.write8(0x48) + g.write8(0x89) + g.write8(0xc1) } else if a == .rdi && b == .rsi { g.write8(0x48) g.write8(0x89) @@ -1076,7 +1158,11 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { fn (mut g Gen) trap() { // funnily works on x86 and arm64 - g.write32(0xcccccccc) + if g.pref.arch == .arm64 { + g.write32(0xcccccccc) + } else { + g.write8(0xcc) + } g.println('trap') } diff --git a/vlib/v/gen/native/gen.v b/vlib/v/gen/native/gen.v index 7d153c4524..74911f37fe 100644 --- a/vlib/v/gen/native/gen.v +++ b/vlib/v/gen/native/gen.v @@ -47,6 +47,12 @@ mut: size_pos []int nlines int callpatches []CallPatch + strs []String +} + +struct String { + str string + pos int } struct CallPatch { @@ -176,6 +182,12 @@ pub fn (g &Gen) pos() i64 { return g.buf.len } +fn (mut g Gen) write(bytes []byte) { + for _, b in bytes { + g.buf << b + } +} + fn (mut g Gen) write8(n int) { // write 1 byte g.buf << byte(n) diff --git a/vlib/v/gen/native/macho.v b/vlib/v/gen/native/macho.v index b25bf05a1d..c7aee25b03 100644 --- a/vlib/v/gen/native/macho.v +++ b/vlib/v/gen/native/macho.v @@ -395,9 +395,14 @@ fn (mut g Gen) write_symbol(s Symbol) { fn (mut g Gen) sym_string_table() int { begin := g.buf.len g.zeroes(1) - at := i64(0x100000000) for i, s in g.strings { - g.write64_at(at + g.buf.len, int(g.str_pos[i])) + pos := g.buf.len - int(g.str_pos[i]) + if g.pref.os == .windows { + g.write32_at(int(g.str_pos[i]), pos - 4) // 0x402028 + pos) + } else { + baddr := i64(0x100000000) + g.write64_at(g.buf.len + baddr, int(g.str_pos[i])) + } g.write_string(s) g.write8(0) } diff --git a/vlib/v/gen/native/pe.v b/vlib/v/gen/native/pe.v index d23794d1f6..ed42685d09 100644 --- a/vlib/v/gen/native/pe.v +++ b/vlib/v/gen/native/pe.v @@ -1,9 +1,16 @@ module native enum PeCharacteristics { - executable_image = 0x102 + // 1: relocation info stripped + // 2: file is executable + // 4: line numbers stripped + // 8: local symbols stripped + // 20: app can handle > 2GB + executable_image = 0x2f } +const image_base = i64(0x400000) + enum PeMachine { i386 = 0x14c amd64 = 0x8664 @@ -18,14 +25,14 @@ enum PeHeader { pub fn (mut g Gen) write_dos_header() { dos_header := [ int(PeHeader.mz), - 0x90, // usedbytesinthelastpage - 3, // filesizeinpages - 0, // numofrelocs - 2, // header size in paragraph - 0, // minimum extra paragraphs - -1, // maximum extra paragraphs + 0x80, // bytes on last page of file + 1, // pages in file + 0, // relocations + 4, // header size in paragraph + 0x10, // minimum extra paragraphs + 0xffff, // maximum extra paragraphs 0, // initial relative ss - 0xb8, // initial SP + 0x140, // initial SP 0, // checksum 0, // IP 0, // IP relative CS @@ -47,14 +54,14 @@ pub fn (mut g Gen) write_dos_header() { 0, 0, 0, - 0xf8, + 0x80, 0, // address of PE header ] for b in dos_header { g.write16(b) } if g.buf.len != 0x40 { - // g.warning('Invalid dos header size') + g.n_error('Invalid dos header size') } } @@ -65,9 +72,46 @@ pub fn (mut g Gen) write_dos_stub() { g.write8(0xba) // richend g.write8(0x0e) // richend // TODO: add a stub for DOS/16, we only run on amd64 - for g.buf.len < 0xf8 { - g.write8(0) // entries - } + pad_to(mut g.buf, 0x80) +} + +fn (mut g Gen) write_pe_sections() { + pad_to(mut g.buf, 0x180) + g.write64(0) + g.write_string_with_padding('.idata', 8) + g.write32(0x89) // 137 + g.write16(0x1000) + g.write32(0x02000000) + g.write32(0x02000000) + g.zeroes(14) + g.write16(64) + g.write8(0) + g.write8(192) + g.write_string_with_padding('.text', 8) + g.write32(75) + g.write8(0) + g.write32(0x20) + g.write32(0x00002) + g.write32(4) + g.write32(0) + g.write32(0) + g.write32(0x20000000) // 0, 0, 0, 32, + g.write([byte(0), 0, 96]) + g.zeroes(52) + g.write([byte(72), 16, 0, 0]) + g.write([byte(40), 16, 0, 0]) + g.zeroes(20) + g.write([byte(96), 16, 0, 0]) + g.write32(0) + g.write([byte(110), 16, 0, 0]) + g.write32(0) + g.write([byte(125), 16, 0, 0]) + g.zeroes(12) + g.write_string_with_padding('KERNEL32.DLL', 13) + g.write_string_with_padding('USER32.DLL', 13) + g.write_string_with_padding('ExitProcess', 14) + g.write_string_with_padding('GetStdHandle', 15) + g.write_string_with_padding('WriteFile', 13) } pub fn (mut g Gen) write_pe_header() { @@ -75,7 +119,7 @@ pub fn (mut g Gen) write_pe_header() { int(PeHeader.pe), 0, int(PeMachine.amd64), // machine - 1, // number of sections + 2, // number of sections 0, 0, // timestamp 0, @@ -83,55 +127,59 @@ pub fn (mut g Gen) write_pe_header() { 0, // number of symbols 0, 0xf0, // 40 // size of optional header - int(PeCharacteristics.executable_image), // c - // 0 // optional header magic + int(PeCharacteristics.executable_image), ] for b in pe_header { g.write16(b) } + // optional here comes here p_opthdr := g.buf.len // should be 0x110 - if p_opthdr != 0x110 { - eprintln('Invalid optdr location') + if p_opthdr != 0x98 { + eprintln('Invalid optdr location $p_opthdr != 0x98') } - g.write16(0x20b) // magic (0x10b=pe32, 0x20b=pe64) - g.write8(0xe) // major linker version - g.write8(0x1d) // minor linker version - g.write32(0x10) // sizeofcode - g.write32(0x10) // initial data size - g.write32(0) // sizeof sizeof uninit data + g.write16(0x20b) // magic (0x10b=pe32, 0x20b=pe32+) + g.write8(0x1) // major linker version + g.write8(0x49) // minor linker version + g.write32(0x200) // sizeofcode + g.write32(0x200) // initial data size + g.write32(0) // sizeof uninit data - image_base := i64(0x140000000) - g.write32(0x1188) // paddr of map // aligned to 4 bytes // entrypoint - g.write32(0x1000) // base of code // aligned to 4 bytes - g.write64(image_base) // image base vaddr // va // aligned to 4 bytes + g.write32(0x2000) // paddr of map // aligned to 4 bytes // entrypoint + g.write32(0x2000) // base of code // aligned to 4 bytes + g.write64(native.image_base) // image base vaddr // va // aligned to 4 bytes g.write32(0x1000) // SectionAlignment g.write32(0x200) // FileAlignment - g.write16(6) // Major OS Version + g.write16(1) // Major OS Version g.write16(0) // Minor OS Version g.write16(0) // major image version g.write16(0) // minor image version - g.write16(6) // major subsystem version + g.write16(5) // major subsystem version g.write16(0) // minor subsystem version g.write32(0) // win32versionvalue - g.write32(0x1000) // hdrsize + codelen) // sizeofimage - g.write32(0x180) // hdrsize) // sizeofheaders + g.write32(0x3000) // hdrsize + codelen) // sizeofimage + g.write32(0x200) // hdrsize) // sizeofheaders g.write32(0) // checksum g.write16(3) // subsystem // subsystem // g.write16(0x400) // dll characteristics + g.write16(0) + /* g.write8(0x60) // dll characteristics g.write8(0x81) // dll characteristics - g.write64(0x100000) // SizeOfStackReserve + */ + g.write64(0x1000) // SizeOfStackReserve g.write64(0x1000) // SizeOfStackCommit - g.write64(0x100000) // SizeOfHeapReserve - g.write64(0x1000) // SizeOfHeapCommit + g.write64(0x10000) // SizeOfHeapReserve + g.write64(0) // SizeOfHeapCommit g.write32(0) // LoaderFlags - g.write32(1) // NumberOfRvaAndSizes + g.write32(0x10) // NumberOfRvaAndSizes g.write32(0) g.write32(0) + g.write32(0x1000) + g.write32(0x100) // size of code } fn (mut g Gen) write_pe_section() { @@ -155,30 +203,35 @@ pub fn (mut g Gen) generate_pe_header() { g.write_dos_header() g.write_dos_stub() g.write_pe_header() + g.write_pe_sections() + + pad_to(mut g.buf, 0x400) g.code_start_pos = g.buf.len g.call(0x18e) g.ret() + g.main_fn_addr = g.buf.len +} + +fn pad_to(mut buf []byte, len int) { + for buf.len < len { + buf << byte(0) + } } pub fn (mut g Gen) generate_pe_footer() { - /* - // TODO: when proper code is generated, uncomment this - codesize := g.buf.len - g.code_start_pos - delta := int(g.code_start_pos) // header_size - // patch the size depending on the codesize - for o in g.size_pos { - n := g.read32_at(o) - g.write32_at(o, n + delta) - } - */ - for g.buf.len < 0x200 { - g.write8(0) // entries - } - - g.write_pe_section() + g.sym_string_table() + pad_to(mut g.buf, 0x600) g.file_size_pos = g.buf.len - g.main_fn_addr = g.buf.len - g.code_start_pos = g.buf.len + + // patch call main + if g.pref.arch == .arm64 { + bl_next := u32(0x94000001) + g.write32_at(g.code_start_pos, int(bl_next)) + } else { + // +1 is for "e8" + // -5 is for "e8 00 00 00 00" + g.write32_at(g.code_start_pos + 1, int(g.main_fn_addr - g.code_start_pos) - 5) + } g.create_executable() }