Improved Windows DLL calls

This commit is contained in:
Eduard Urbach 2024-08-19 11:11:45 +02:00
parent 0db54ff639
commit 05789d9626
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
12 changed files with 124 additions and 68 deletions

View File

@ -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)
}

View File

@ -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:

View File

@ -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 {

View File

@ -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:

View File

@ -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)
}

View File

@ -45,5 +45,3 @@ func Reset() {
TargetOS = "mac"
}
}
//

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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",
},
},
}

9
src/register/DLLCall.go Normal file
View File

@ -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()
}