Improved Windows DLL calls
This commit is contained in:
parent
0db54ff639
commit
05789d9626
@ -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)
|
||||
}
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -45,5 +45,3 @@ func Reset() {
|
||||
TargetOS = "mac"
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
9
src/register/DLLCall.go
Normal 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()
|
||||
}
|
Loading…
Reference in New Issue
Block a user