From 05789d9626f9a59debf2150ab48264ca79abef43 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 19 Aug 2024 11:11:45 +0200 Subject: [PATCH] Improved Windows DLL calls --- lib/sys/sys_windows.q | 17 +++++++-------- src/asm/Finalize.go | 48 +++++++++++++++++++++++++---------------- src/asm/Instructions.go | 10 +++++++++ src/asm/Mnemonic.go | 6 +++--- src/compiler/Result.go | 31 +++++++++++++++----------- src/config/config.go | 2 -- src/core/CompileCall.go | 20 ++++++++++++++++- src/core/Function.go | 2 ++ src/dll/List.go | 20 +++++++++++++++++ src/exe/pe/EXE.go | 6 ++++-- src/os/windows/DLLs.go | 21 ------------------ src/register/DLLCall.go | 9 ++++++++ 12 files changed, 124 insertions(+), 68 deletions(-) delete mode 100644 src/os/windows/DLLs.go create mode 100644 src/register/DLLCall.go diff --git a/lib/sys/sys_windows.q b/lib/sys/sys_windows.q index 575bb00..0ebf0a6 100644 --- a/lib/sys/sys_windows.q +++ b/lib/sys/sys_windows.q @@ -1,14 +1,13 @@ -write(_ Int, _ Pointer, _ Int) -> Int { - // WriteFile(fd, buffer, length, out numberOfBytesWritten, out overlapped) - return 0 +write(fd Int, address Pointer, length Int) -> Int { + // out numberOfBytesWritten + // out overlapped + return kernel32.WriteFile(fd, address, length) } -mmap(_ Int, _ Int, _ Int, _ Int) -> Pointer { - // VirtualAlloc(address, length, flags, protection) - return 0 +mmap(address Int, length Int, protection Int, flags Int) -> Pointer { + return kernel32.VirtualAlloc(address, length, flags, protection) } -exit(_ Int) { - // ExitProcess(code) - return +exit(code Int) { + kernel32.ExitProcess(code) } \ No newline at end of file diff --git a/src/asm/Finalize.go b/src/asm/Finalize.go index de3eae6..676eaba 100644 --- a/src/asm/Finalize.go +++ b/src/asm/Finalize.go @@ -113,24 +113,6 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { codePointers = append(codePointers, pointer) - case CALL_AT: - code = x64.CallAtAddress(code, 0x00_00_00_00) - size := 4 - label := x.Data.(*Label) - - pointer := &Pointer{ - Position: Address(len(code) - size), - OpSize: 2, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - index := dlls.Index("kernel32.dll", label.Name) - return Address(index * 8) - } - - dllPointers = append(dllPointers, pointer) - case COMMENT: continue @@ -142,6 +124,36 @@ func (a Assembler) Finalize(dlls dll.List) ([]byte, []byte) { code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) } + case DLLCALL: + size := 4 + + code = x64.SubRegisterNumber(code, x64.RSP, 32) + code = x64.CallAtAddress(code, 0x00_00_00_00) + position := len(code) - size + code = x64.AddRegisterNumber(code, x64.RSP, 32) + + label := x.Data.(*Label) + pointer := &Pointer{ + Position: Address(position), + OpSize: 2, + Size: uint8(size), + } + + pointer.Resolve = func() Address { + dot := strings.Index(label.Name, ".") + library := label.Name[:dot] + funcName := label.Name[dot+1:] + index := dlls.Index(library, funcName) + + if index == -1 { + panic("unknown DLL function " + label.Name) + } + + return Address(index * 8) + } + + dllPointers = append(dllPointers, pointer) + case JE, JNE, JG, JGE, JL, JLE, JUMP: switch x.Mnemonic { case JE: diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index ced8cf5..acfa639 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -20,6 +20,16 @@ func (a *Assembler) Call(name string) { }) } +// DLLCall calls a function in a DLL file. +func (a *Assembler) DLLCall(name string) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: DLLCALL, + Data: &Label{ + Name: name, + }, + }) +} + // Return returns back to the caller. func (a *Assembler) Return() { if len(a.Instructions) > 0 { diff --git a/src/asm/Mnemonic.go b/src/asm/Mnemonic.go index 36687b7..c1e61e0 100644 --- a/src/asm/Mnemonic.go +++ b/src/asm/Mnemonic.go @@ -29,7 +29,7 @@ const ( // Control flow CALL - CALL_AT + DLLCALL JE JNE JG @@ -55,8 +55,8 @@ func (m Mnemonic) String() string { return "and" case CALL: return "call" - case CALL_AT: - return "call at" + case DLLCALL: + return "dllcall" case COMMENT: return "comment" case COMPARE: diff --git a/src/compiler/Result.go b/src/compiler/Result.go index fda6953..7c9a7c6 100644 --- a/src/compiler/Result.go +++ b/src/compiler/Result.go @@ -10,6 +10,7 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/core" + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/exe/elf" "git.akyoto.dev/cli/q/src/exe/macho" "git.akyoto.dev/cli/q/src/exe/pe" @@ -27,7 +28,7 @@ type Result struct { } // finalize generates the final machine code. -func (r *Result) finalize() ([]byte, []byte) { +func (r *Result) finalize() ([]byte, []byte, dll.List) { // This will be the entry point of the executable. // The only job of the entry function is to call `main` and exit cleanly. // The reason we call `main` instead of using `main` itself is to place @@ -49,15 +50,22 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 0) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 32+8) - final.RegisterNumber(asm.MOVE, x64.RCX, 0) - final.Label(asm.CALL_AT, "ExitProcess") + final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 0) + final.DLLCall("kernel32.ExitProcess") } + dlls := dll.List{} + // This will place the main function immediately after the entry point // and also add everything the main function calls recursively. r.eachFunction(r.Main, map[*core.Function]bool{}, func(f *core.Function) { final.Merge(f.Assembler) + + for _, library := range f.DLLs { + for _, fn := range library.Functions { + dlls = dlls.Append(library.Name, fn) + } + } }) final.Label(asm.LABEL, "_crash") @@ -72,13 +80,12 @@ func (r *Result) finalize() ([]byte, []byte) { final.RegisterNumber(asm.MOVE, x64.SyscallInputRegisters[1], 1) final.Syscall() case "windows": - final.RegisterNumber(asm.SUB, x64.RSP, 32+8) final.RegisterNumber(asm.MOVE, windows.X64InputRegisters[0], 1) - final.Label(asm.CALL_AT, "ExitProcess") + final.Label(asm.DLLCALL, "kernel32.ExitProcess") } - code, data := final.Finalize(windows.DLLs) - return code, data + code, data := final.Finalize(dlls) + return code, data, dlls } // eachFunction recursively finds all the calls to external functions. @@ -116,8 +123,8 @@ func (r *Result) PrintInstructions() { // Write writes the executable to the given writer. func (r *Result) Write(writer io.Writer) error { - code, data := r.finalize() - return write(writer, code, data) + code, data, dlls := r.finalize() + return write(writer, code, data, dlls) } // Write writes an executable file to disk. @@ -145,7 +152,7 @@ func (r *Result) WriteFile(path string) error { } // write writes an executable file to the given writer. -func write(writer io.Writer, code []byte, data []byte) error { +func write(writer io.Writer, code []byte, data []byte, dlls dll.List) error { buffer := bufio.NewWriter(writer) switch config.TargetOS { @@ -154,7 +161,7 @@ func write(writer io.Writer, code []byte, data []byte) error { case "mac": macho.Write(buffer, code, data) case "windows": - pe.Write(buffer, code, data, windows.DLLs) + pe.Write(buffer, code, data, dlls) default: return fmt.Errorf("unsupported platform '%s'", config.TargetOS) } diff --git a/src/config/config.go b/src/config/config.go index 79f7d58..32153a1 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -45,5 +45,3 @@ func Reset() { TargetOS = "mac" } } - -// diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 14ae685..2bf126a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,9 +1,12 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/os/windows" "git.akyoto.dev/cli/q/src/types" ) @@ -31,7 +34,22 @@ func (f *Function) CompileCall(root *expression.Expression) (*Function, error) { name = nameNode.Children[1].Token.Text(f.File.Bytes) } - if pkg != f.File.Package { + if pkg == "kernel32" || pkg == "user32" || pkg == "gdi32" || pkg == "comctl32" { + parameters := root.Children[1:] + registers := windows.X64InputRegisters[:len(parameters)] + + for i := len(parameters) - 1; i >= 0; i-- { + _, err := f.ExpressionToRegister(parameters[i], registers[i]) + + if err != nil { + return nil, err + } + } + + f.DLLs = f.DLLs.Append(pkg, name) + f.DLLCall(fmt.Sprintf("%s.%s", pkg, name)) + return nil, nil + } else if pkg != f.File.Package { if f.File.Imports == nil { return nil, errors.New(&errors.UnknownPackage{Name: pkg}, f.File, nameNode.Token.Position) } diff --git a/src/core/Function.go b/src/core/Function.go index 121ae8c..4694800 100644 --- a/src/core/Function.go +++ b/src/core/Function.go @@ -1,6 +1,7 @@ package core import ( + "git.akyoto.dev/cli/q/src/dll" "git.akyoto.dev/cli/q/src/fs" "git.akyoto.dev/cli/q/src/register" "git.akyoto.dev/cli/q/src/scope" @@ -19,6 +20,7 @@ type Function struct { Parameters []*scope.Variable ReturnTypes []*types.Type Functions map[string]*Function + DLLs dll.List Err error deferred []func() count counter diff --git a/src/dll/List.go b/src/dll/List.go index b031981..236de5f 100644 --- a/src/dll/List.go +++ b/src/dll/List.go @@ -3,6 +3,26 @@ package dll // List is a slice of DLLs. type List []DLL +// Append adds a function for the given DLL if it doesn't exist yet. +func (list List) Append(dllName string, funcName string) List { + for _, dll := range list { + if dll.Name != dllName { + continue + } + + for _, fn := range dll.Functions { + if fn == funcName { + return list + } + } + + dll.Functions = append(dll.Functions, funcName) + return list + } + + return append(list, DLL{Name: dllName, Functions: []string{funcName}}) +} + // Index returns the position of the given function name. func (list List) Index(dllName string, funcName string) int { index := 0 diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 60a7a79..726fcc4 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "io" + "time" "git.akyoto.dev/cli/q/src/config" "git.akyoto.dev/cli/q/src/dll" @@ -38,6 +39,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { functionsStart := len(imports) * 8 dllNamePos := len(dllData) dllData = append(dllData, library.Name...) + dllData = append(dllData, ".dll"...) dllData = append(dllData, 0x00) dllImports = append(dllImports, DLLImport{ @@ -95,7 +97,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { Signature: [4]byte{'P', 'E', 0, 0}, Machine: IMAGE_FILE_MACHINE_AMD64, NumberOfSections: uint16(NumSections), - TimeDateStamp: 0, + TimeDateStamp: uint32(time.Now().Unix()), PointerToSymbolTable: 0, NumberOfSymbols: 0, SizeOfOptionalHeader: OptionalHeader64Size, @@ -124,7 +126,7 @@ func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) { SizeOfHeaders: config.CodeOffset, // section bodies begin here CheckSum: 0, Subsystem: IMAGE_SUBSYSTEM_WINDOWS_CUI, - DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, + DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE SizeOfStackReserve: 0x100000, SizeOfStackCommit: 0x1000, SizeOfHeapReserve: 0x100000, diff --git a/src/os/windows/DLLs.go b/src/os/windows/DLLs.go deleted file mode 100644 index 3b148ac..0000000 --- a/src/os/windows/DLLs.go +++ /dev/null @@ -1,21 +0,0 @@ -package windows - -import "git.akyoto.dev/cli/q/src/dll" - -// Temporary fix... -var DLLs = dll.List{ - { - Name: "kernel32.dll", - Functions: []string{ - "ExitProcess", - "GetStdHandle", - "WriteFile", - }, - }, - { - Name: "user32.dll", - Functions: []string{ - "MessageBoxA", - }, - }, -} diff --git a/src/register/DLLCall.go b/src/register/DLLCall.go new file mode 100644 index 0000000..c62eb55 --- /dev/null +++ b/src/register/DLLCall.go @@ -0,0 +1,9 @@ +package register + +import "git.akyoto.dev/cli/q/src/asm" + +func (f *Machine) DLLCall(label string) { + f.Assembler.Label(asm.DLLCALL, label) + f.UseRegister(f.CPU.Output[0]) + f.postInstruction() +}